Node Fundamentals – Part 2
In this chapter, we'll continue our discussion on some more node fundamentals. We'll explore yargs, and
we'll see how to parse command-line arguments using process.argv and yargs. After that, we'll explore
JSON. JSON is nothing more than a string that looks kind of like a JavaScript object, with the notable
differences being that it uses double quotes instead of single quotes and all of your property names—like
name and age, in this case—require quotes around them. We'll look into how to convert an object into a
string, then define that string, use it, and convert it back to an object.
After we've done that, we'll fill out the addNote function. Finally, we'll look into refactor, moving the
functionality into individual functions and testing the functionality.
More specifically, we'll go through following topics: yargs
JSON
Adding note Refactor
yargs
In this section, we will use yargs, a third-party npm module, to make the process of parsing much easier. It
will let us access things such as title and body information without needing to write a manual parser. This
is a great example of when you should look for an npm module. If we don't use a module, it would be
more productive for our Node application to use a third-party module that has been tested and thoroughly
vetted.
To get started, we'll install the module, then we'll add it into the project, parsing for things such as a title of
the body, and we'll call all the functions that will get defined over in notes.js. If the command is add, we'll
call add note, so on.
Installing yargs
Now, let's view the documents page for yargs. It's always a good idea to know what you're getting yourself
into. If you search for yargs on Google, you should find the GitHub page as your first search result. As
shown in the following screenshot, we have the GitHub page for the yargs library:
Now, yargs is a very complex library. It has a ton of features for validating all sorts of input, and it has
different ways in which you can format that input. We will start with a very basic example, although we
will be introducing more complex examples throughout this chapter.
If you want to look at any other features that we don't discuss in the
chapter, or you just want to see how something works that we have talked about, you can
always find it in the yarg documents.
We'll now move into Terminal to install this module inside of our application. To do this, we'll use npm
install followed by the module name, yargs, and in this case, I'll use the @ sign to specify the specific
version of the module I want to use, 11.0.0, which is the most recent version at the time of writing. Next,
I'll add the save flag, which, as we know, updates the package.json file:
npm install yargs@11.0.0 --save
If I leave off the save flag, yargs will get installed into the node_modules folder, but if we wipe that
node_modules folder later and run npm install, yargs won't get reinstalled because it's not listed in the
package.json file. This is why we use the save flag.
Running yargs
Now that we've installed yargs, we can move over into Atom, inside of app.js, and get started with using it.
The basics of yargs, the very core of its feature set, is really simple to take advantage of. The first thing
we'll do is to require it up, as we did with fs and lodash in the previous chapter. Let's make a constant and
call it yargs, setting it equal to require('yargs'), as shown here:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js');
var command = process.argv[2]; console.log('Command:', command); console.log(process.argv);
if (command === 'add') { console.log('Adding new note');
} else if (command === 'list') { console.log('Listing all notes');
} else if (command === 'read') { console.log('Reading note');
} else if (command === 'remove') { console.log('Removing note');
} else {
console.log('Command not recognized');
}
From here, we can fetch the arguments as yargs parses them. It will take the same process.argv array that
we discussed in the previous chapter, but it goes behind the scenes and parses it, giving us something
that's much more useful than what Node gives us. Just above the command variable, we can make a const
variable called argv, setting it equal to yargs.argv, as shown here:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js');
const argv = yargs.argv;
var command = process.argv[2]; console.log('Command:', command); console.log(process.argv);
if (command === 'add') { console.log('Adding new note');
} else if (command === 'list') { console.log('Listing all notes');
} else if (command === 'read') { console.log('Reading note');
} else if (command === 'remove') { console.log('Removing note');
} else {
console.log('Command not recognized');
}
The yargs.argv module is where the yargs library stores its version of the arguments that your app ran
with. Now we can print it using console.log, and this will let us take a look at the process.argv and
yargs.argv variables; we can also compare them and see how yargs differs. For the command where we
use
console.log to print process.argv, I'll make the first argument a string called Process
so that we can differentiate it in Terminal. We'll call console.log again. The first argument will be the Yargs
string, and the second one will be the actual argv variable, which comes from yargs:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js'); const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command); console.log('Process', process.argv); console.log('Yargs', argv);
if (command === 'add') { console.log('Adding new note');
} else if (command === 'list') { console.log('Listing all notes');
} else if (command === 'read') { console.log('Reading note');
} else if (command === 'remove') { console.log('Removing note');
} else {
console.log('Command not recognized');
}
Now we can run our app (refer to the preceding code block) a few different ways and see how these two
console.log statements differ.
First up, we'll run at node app.js with the add command, and we can run this very basic example:
node app.js add
We already know what the process.argv array looks like from the previous chapter. The useful information
is the third string inside of the array, which is 'add'. In the fourth string, Yargs gives us an object that looks
very different:
As shown in the preceding code output, first we have the underscore property, then commands such as
add are stored.
If I were to add another command, say add, and then I were to add a modifier, say encrypted, you would
see that add would be the first argument and encrypted the second, as shown here:
node app.js add encrypted
So far, yargs really isn't shining. This isn't much more useful than what we have in the previous example.
Where it really shines is when we start passing in key- value pairs, such as the title example we used in the
Getting input section of Node Fundamentals - Part 1 in chapter 2. I can set my title flag equal to secrets,
press enter, and this time around, we get something much more useful:
node app.js add --title=secrets
In the following code output, we have the third string that we would need to parse in order to fetch the
value and the key, and in the fourth string, we actually have a title property with a value of secrets:
Also, yargs has built-in parsing for all the different ways you could specify this.
We can insert a space after title, and it will still work just as it did before; we can add quotes around
secrets, or add other words, like secrets from Andrew, and it will still parses it correctly, setting the title
property to the secrets from Andrew string, as shown here:
node app.js add --title "secrets from Andrew"
This is where yargs really shines! It makes the process of parsing your arguments a lot easier. This means
that inside our app, we can take advantage of that parsing and call the proper functions.
Working with the add command
Let's work with the add command, for example, for parsing your arguments and calling the functions. Once
the add command gets called, we want to call a function defined in notes, which will be responsible for
actually adding the note. The notes.addNote function will get the job done. Now, what do we want to pass
to the addNote function? We want to pass in two things: the title, which is accessible on argv.title, as we
saw in the preceding example; and the body, argv.body:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js'); const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command); console.log('Process', process.argv); console.log('Yargs', argv);
if (command === 'add') { console.log('Adding new note'); notes.addNote(argv.title, argv.body);
} else if (command === 'list') { console.log('Listing all notes');
} else if (command === 'read') { console.log('Reading note');
} else if (command === 'remove') { console.log('Removing note');
} else {
console.log('Command not recognized');
}
Currently, these command-line arguments, title and body, aren't required. So technically, the user could run
the application without one of them, which would cause it to crash, but in future, we'll be requiring both of
these.
Now that we have notes.addNote in place, we can remove our console.log statement, which was just a
placeholder, and we can move into the notes application
notes.js.
Inside notes.js, we'll get started by making a variable with the same name as the method we used over
app.js and addNote, and we will set it equal to an anonymous
arrow function, as shown here:
var addNote = () => {
};
Now, this alone isn't too useful, because we're not exporting the addNote function. Below the variable, we
can define module.exports in a slightly different way. In previous sections, we added properties onto
exports to export them. We can actually define an entire object that gets set to exports, and in this case,
we can set addNote equal to the addNote function defined in preceding code block:
module.exports = { addNote: addNote
};
In ES6, there's actually a shortcut for this. When you're setting an object attribute and a value that's a
variable and they're both exactly the same, you can actually leave off the colon and the value. Either way,
the result identical.
In the preceding code, we're setting an object equal to module.exports, and that object has a property,
addNote, which points to the addNote function we defined as a variable in the preceding code block.
Once again, addNote: and addNote are identical inside of ES6. We will be using the ES6 syntax for
everything throughout this book.
Now I can take my two arguments, title and body, and actually do something with them. In this case, we'll
call console.log and Adding note, passing in the two arguments as the second and third argument to
console.log, title and body, as shown here:
var addNote = (title, body) => { console.log('Adding note', title, body);
};
Now we're in a pretty good position to run the add command with title and body and see if we get exactly
what we'd expect, which is the console.log statement shown in the preceding code to print.
Over in Terminal, we can start by running the app with node app.js, and then specify the filename. We'll
use the add command; which will run the appropriate
function. Then, we'll pass in title, setting it equal to secret, and then we can pass in body, which will be our
second command-line argument, setting that equal to the string, This is my secret:
node app.js add --title=secret --body="This is my secret"
In this command, we specified three things: the add command the title argument, which gets set to secret;
and the body argument, which gets set to "This is my secret". If all goes well, we'll get the appropriate log.
Let's run the command.
In the following command output, you can see Adding note secret, which is the title; and This is my secret,
which is the body:
With this in place, we now have one of our methods set up and ready to go. The next thing that we'll do is
convert the other commands we have—the list, read,
and remove commands. Let's look into one more command, and then you'll do the other two by yourself
as exercises.
Working with the list command
Now, with the list command, I'll remove the console.log statement and call
notes.getAll, as shown here:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js'); const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command); console.log('Process', process.argv); console.log('Yargs', argv);
if (command === 'add') { notes.addNote(argv.title, argv.body);
} else if (command === 'list') {
notes.getAll();
} else if (command === 'read') { console.log('Reading note');
} else if (command === 'remove') { console.log('Removing note');
} else {
console.log('Command not recognized');
}
At some point, notes.getAll will return all of the notes. Now, getAll doesn't take any arguments since it will
return all of the notes regardless of the title. The read command will require a title, and remove will also
require the title of the note you want to remove.
For now, we can create the getAll function. Inside notes.js, we'll go through that process again. We'll start
by making a variable, calling it getAll, and setting it equal to an arrow function, which we've used before.
We start with our arguments list, then we set up the arrow (=>), which is the equal sign and the greater
than sign. Next, we specify the statements we want to run. Inside our code block, we'll run
console.log(Getting all notes), as shown here:
var getAll = () => { console.log('Getting all notes');
};
The last step to the process after adding that semicolon will be to add getAll to the exports, as shown in
the following code block:
module.exports = { addNote,
getAll
};
Remember that in ES6, if you have a property whose name is identical to the value, which is a variable, you
can simply remove the value variable and the colon.
Now that we have getAll in notes.js in place, and we've wired it up in app.js, we can run things over in
Terminal. In this case, we'll run the list command:
node app.js list
In the preceding code output, you can see at the bottom that Getting all notes
prints to the screen. Now that we have this in place, we can remove console.log('Process', process.argv)
from the command variable in app.js. The resultant code will look like the following code block:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js'); const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command); console.log('Yargs', argv);
if (command === 'add') { notes.addNote(argv.title, argv.body);
} else if (command === 'list') { notes.getAll();
} else if (command === 'read') { console.log('Reading note');
} else if (command === 'remove') { console.log('Removing note');
} else {
console.log('Command not recognized');
}
We will keep the yargs log around since we'll be exploring the other ways and methods to use yargs
throughout the chapter.
Now that we have the list command in place, next, I'd like you to create a method for the read and remove
commands.
The read command
When the read command is used, we want to call notes.getNote, passing in title. Now, title will get passed
in and parsed using yargs, which means that we can use argv.title to fetch it. And that's all we have to do
when it comes to calling the function:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js'); const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command); console.log('Yargs', argv);
if (command === 'add') { notes.addNote(argv.title, argv.body);
} else if (command === 'list') { notes.getAll();
} else if (command === 'read') {
notes.getNote(argv.title);
} else if (command === 'remove') { console.log('Removing note');
} else {
console.log('Command not recognized');
}
The next step is to define getNote, because currently it doesn't exist. Over in
notes.js, right below the getAll variable, we can make a variable called getNote,
which will be a function. We'll use the arrow function, and it will take an argument; it will take the note
title. The getNote function takes the title, then it returns the body for that note:
var getNote = (title) => {
};
Inside getNote, we can use console.log to print something like Getting note, followed by the title of the
note you will fetch, which will be the second argument to
console.log:
var getNote = (title) => { console.log('Getting note', title);
};
This is the first command, and we can now test it before we go on to the second one, which is remove.
Over in Terminal, we can use node app.js to run the file. We'll be using the new read command, passing in
a title flag. I'll use a different syntax, where title gets set equal to the value outside of quotes. I'll use
something like accounts:
node app.js read --title accounts
This accounts value will read the accounts note in the future, and it will print it to the screen, as shown
here:
As you can see in the preceding code output, we get an error, which we'll debug now.
Dealing with the errors in parsing commands
Getting an error is not the end of the world. Getting an error usually means that you have a small typo or
you forgot one step in the process. So, we'll first figure out how to parse through these error messages,
because the error messages you get in the code output can be pretty daunting. Let's refer to the code
output error here:
As you can see, the first line shows you where the error occurred. It's inside of our app.js file, and the
number 19 after the colon is the line number. It shows you
exactly where things went bad. The TypeError: notes.getNote is not a function line is telling you pretty
clearly that the getNote function you tried to run doesn't exist. Now we can take this information and
debug our app.
In app.js, we see that we call notes.getNote. Everything looks great, but when we move into notes.js, we
realize that we never actually exported getNote. This is why when we try to call the function, we get
getNote is not a function. All we have to do to fix that error message is export getNote, as shown here:
module.exports = { addNote, getAll,
getNote
};
Now when we save the file and rerun the app from Terminal, we'll get what we expect—Getting note
followed by the title, which is accounts, as shown here:
This is how we can debug our error messages. Error messages contain really useful information. For the
most part, the first couple of lines are code that you've written, and the other ones are internal Node code
or third-party modules. In our case, the first line of the stack trace is important, as it shows exactly where
the error occurred.
The remove command
Now, since the read command is working, we can move on to the last one, which is the remove command.
Here, I'll call notes.removeNote, passing in the title, which as we know is available in argv.title:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js'); const argv = yargs.argv;
var command = process.argv[2];
console.log('Command:', command); console.log('Yargs', argv);
if (command === 'add') { notes.addNote(argv.title, argv.body);
} else if (command === 'list') { notes.getAll();
} else if (command === 'read') { notes.getNote(argv.title);
} else if (command === 'remove') {
notes.removeNote(argv.title);
} else {
console.log('Command not recognized');
}
Next up, we'll define the removeNote function over inside of our notes API file, right below the getNote
variable:
var removeNote = (title) => { console.log('Removing note', title);
};
Now, removeNote will work much the same way as getNote. All it needs is the title; it can use this
information to find the note and remove it from the database. This will be an arrow function that takes the
title argument.
In this case, we'll print the console.log statement, Removing note; then, as the second argument, we'll
simply print title back to the screen to make sure that it's going through the process successfully. This time
around, we'll export our removeNote function; we'll define it using the ES6 syntax:
module.exports = { addNote, getAll, getNote, removeNote
};
The last thing to do is test it and make sure it works. We can reload the last command using the up arrow
key. We change read to remove, and that is all we need to do. We're still passing in the title argument,
which is great, because that is what remove needs:
node app.js remove --title accounts
When I run this command, we get exactly what we expected. Removing note prints to the screen, as
shown in the following code output, and then we get the title of the note that we're supposed to be
removing, which is accounts:
This looks great! That is all it takes to use yargs to parse your arguments.
With this, we now have a place to define all of that functionality, for saving, reading, listing, and removing
notes.
Fetching command
The last thing I want to discuss before we wrap up this section is—how we fetch
command.
As we know, command is available in the _ property as the first and only item. This means that in the
app.js, var command statement, we can set command equal to argv, then ._, and then we'll use [] to grab
the first item in the array, as shown in the following code:
console.log('Starting app.js');
const fs = require('fs'); const _ = require('lodash'); const yargs = require('yargs');
const notes = require('./notes.js'); const argv = yargs.argv;
var command = argv._[0];
console.log('Command:', command); console.log('Yargs', argv);
if (command === 'add') { notes.addNote(argv.title, argv.body);
} else if (command === 'list') { notes.getAll();
} else if (command === 'read') { notes.getNote(argv.title);
} else if (command === 'remove') { notes.removeNote(argv.title);
} else {
console.log('Command not recognized');
}
With this in place, we now have the same functionality, but we'll use yargs everywhere. If I rerun the last
command, we can test that the functionality still works. And it does! As shown in the following command
output, we can see that Command: remove shows up:
Next, we'll look into filling out the individual functions. We'll take a look first at how we can use JSON to
store our notes inside our file system.
JSON
Now that you know how to parse command-line arguments using process.argv and yargs, you've solved
the first piece to the puzzle for the notes application. Now, how do we get that unique input from the
user? The second piece to the puzzle is to solve how we store this information.
When someone adds a new note, we want to save it somewhere, preferably on the filesystem. So the next
time they try to fetch, remove, or read that note, they actually get the note back. To do this, we'll need to
introduce something called JSON. If you're already familiar with JSON, you probably know it is super
popular. It stands for JavaScript Object Notation, and it's a way to represent JavaScript arrays and objects
using a string. Now, why would you ever want to do that?
Well, you might want to do that because strings are just text, and that's pretty much supported anywhere.
I can save JSON to a text file, and then I can read it later, parse it back into a JavaScript array or object, and
do something with it. This is exactly what we'll take a look at in this section.
To explore JSON and how it works, let's go ahead and make a new folder inside our project called
playground.
Throughout the book, I'll create the playground folders and various projects, which store simple one-off files
that aren't a part of the bigger application; they're just a way to explore a new feature or learn a new
concept.
In the playground folder, we'll make a file called json.js, this is where we can explore how JSON works. To
get started, let's make a very simple object.
Converting objects into strings
Let's first make a variable called obj, setting it equal to an object. On this object, we'll just define one
property, name, and set it equal to your first name; I'll set this one equal to Andrew, as shown here:
var obj = { name: 'Andrew'
};
Now, let's assume that we want to take this object and work on it. Let's say we want to, for example, send
it between servers as a string and save it to a text file. To do this, we'll need to call one JSON method.
Let's take a moment to define a variable to store the result, stringObj, and we'll set it equal to
JSON.stringify, as shown here:
var stringObj = JSON.stringify(obj);
The JSON.stringify method takes your object, in this case, the obj variable, and returns the JSON-stringified
version. This means that the result stored in stringObj is actually a string. It's no longer an object, and we
can take a look at that using console.log. I'll use console.log twice. First up, we'll use the typeof operator to
print the type of the string object to make sure that it actually is a string. Since typeof is an operator, it gets
typed in lowercase, there is no camel casing. Then, you pass in the variable whose type you want to check.
Next up, we can use console.log to print the contents of the string itself, printing out the stringObj
variable, as shown here:
console.log(typeof stringObj); console.log(stringObj);
What we've done here is we've taken an object, converted it into a JSON string, and printed it onto the
screen. Over in Terminal, I'll navigate into the playground folder using the following command:
cd playground
For now, it doesn't matter where you run the command, but in future it will matter when we are in the
playground folder, so take a
moment to navigate into it.
We can now use node to run our json.js file. When we run the file, we see two things:
As shown in the preceding code output, first, we will get our type, which is a string, and this is great,
because remember, JSON is a string. Next, we will get our object, which looks pretty similar to a JavaScript
object, but there are a few differences. These differences are as follows:
First up, your JSON will have its attribute names automatically wrapped in double quotes. This is a
requirement of the JSON syntax.
Next up, you'll notice your strings are also wrapped in double quotes as opposed to single quotes.
Now, JSON doesn't just support string values, you can use an array, a Boolean, a number, or anything else.
All of those types are perfectly valid inside of your JSON. In this case, we have a very simple example
where we have a name property and it's set to "Andrew".
This is the process of taking an object and converting it into a string. Next up, we'll define a string and
convert that into an object we can actually use in our
app.
Defining a string and using in app as an object
Let's get started by making a variable called personString, and we'll to set it equal to a string using single
quotes since JSON uses double quotes inside of itself, as shown here:
var personString = '';
Then we'll define our JSON in the quotes. We'll start by opening and closing some curly braces. We'll use
double quotes to create our first attribute, which we'll call name, and we'll set that attribute equal to
Andrew. This means that after the closing quote, we'll add :; then we'll open and close double quotes again
and type the value Andrew, as shown here:
var personString = '{"name": "Andrew"}';
Next up, we can add another property. After the value, Andrew, I'll create another property after the
comma, called age, which will be set equal to a number. I can use my colon and then define the number
without the quotes, in this case, 25:
var personString = '{"name": "Andrew","age": 25}';
You can go ahead and use your name and your age, obviously, but make sure the rest looks identical to
what you see here.
Now, let's say we get the earlier-defined JSON from a server or we grab it from a text file. Currently, it's
useless; if we want to get the name value, there is no good way to do that because we're using a string, so
personString.name doesn't exist. What we need to do is take the string and convert it back into an object.
Converting a string back to an object
To convert the string back to object, we'll use the opposite of JSON.stringify, which is JSON.parse. Let's
make a variable to store the result. I'll create a person variable and it will be set equal to JSON.parse,
passing in as the one and only argument the string you want to parse, in this case, the person string, which
we defined earlier:
var person = JSON.parse(personString);
Now, this variable takes your JSON and converts it from a string back into its original form, which could be
an array or an object. In our case, it converts it back into an object, and we have the person variable as an
object, as shown in the preceding code. Also, we can prove that it's an object using the typeof operator. I'll
use console.log twice, just like we did previously.
First up, we'll print typeof person, and then we'll print the actual person variable,
console.log(person):
console.log(typeof person); console.log(person);
With this in place, we can now rerun the command in Terminal; I'll actually start
nodemon and pass in json.js:
nodemon json.js
As shown in the following code output, you can now see that we're working with an object, which is great,
and we have our regular object:
We know that Andrew is an object because it's not wrapped in double quotes; the values don't have any
quotes, and we use single quotes for Andrew, which is valid in JavaScript, but it's not valid in JSON.
This is the entire process of taking an object, converting it to a string, and then taking the string and
converting it back into the object, and this is exactly what we'll do in the notes app. The only difference is
that we'll be taking the following string and storing it in a file, then later on, we'll be reading that string
from the file using JSON.parse to convert it back to an object, as shown in the following code block:
// var obj = {
// name: 'Andrew'
// };
// var stringObj = JSON.stringify(obj);
// console.log(typeof stringObj);
// console.log(stringObj);
var personString = '{"name": "Andrew","age": 25}'; var person = JSON.parse{personString};
console.log(typeof person);
console.log(person);
Storing the string in a file
With the basics in place, let's take it just one step further, that is, by storing the string in a file. Then, we
want to read the contents of that file back by using the fs module and printing some properties from it.
This means that we'll need to convert the string that we get back from fs.readfilesync into an object using
JSON.parse.
Writing the file in the playground folder
Let's go ahead and comment out all the code we have so far and start with a clean slate. First up, let's go
ahead and load in the fs module. The const variable fs will be set equal to require, and we'll pass the fs
module that we've used in the past, as shown here:
// var obj = {
// name: 'Andrew'
// };
// var stringObj = JSON.stringify(obj);
// console.log(typeof stringObj);
// console.log(stringObj);
// var personString = '{"name": "Andrew","age": 25}';
// var person = JSON.parse(personString);
// console.log(typeof person);
// console.log(person); const fs = require('fs');
The next thing we'll do is define the object. This object will be stored inside of our file, and then will be
read back and parsed. This object will be a variable called originalNote, and we'll call it originalNote
because later on, we'll load it back in and call that variable Note.
Now, originalNote will be a regular JavaScript object with two properties. We'll have the title property,
which we'll set equal to Some title, and the body property, which we will set equal to Some body, as
shown here:
var originalNote = { title: 'Some title', body: 'Some body'
};
The next step that you will need to do is take the original note and create a variable called
originalNoteString, and set that variable equal to the JSON value of the object we defined earlier. This
means that you'll need to use one of the two JSON methods we used previously in this section.
Now, once you have that originalNoteString variable, we can write a file to the
filesystem. I'll write that line for you, fs.writeFileSync. The writeFileSync method, which we used before,
takes two arguments. One will be the filename, and since we're using JSON, it's important to use the JSON
file extension. I'll call this file notes.json. The other arguments will be text content, originalNoteString,
which is not yet defined, as shown in this code block:
// originalNoteString fs.writeFileSync('notes.json', originalNoteString);
This is the first step to the process; this is how we'll write that file into the playground folder. The next step
to the process will be to read out the contents, parse it using the JSON method earlier, and print one of the
properties to the screen to make sure that it's an object. In this case, we'll print the title.
Reading out the content in the file
The first step to print the title is to use a method we haven't used yet. We'll use the read method available
on the filesystem module to read the contents. Let's make a variable called noteString. The noteString
variable will be set equal to
fs.readFileSync.
Now, readFileSync is similar to writeFileSync except that it doesn't take the text content, since it's getting
the text content back for you. In this case, we'll just specify the first argument, which is the filename,
notes.JSON:
var noteString = fs.readFileSync('notes.json');
Now that we have the string, it will be your job to take that string, use one of the preceding methods, and
convert it back into an object. You can call that variable note. Next up, the only thing left to do is to test
whether things are working as expected, by printing with the help of console.log(typeof note). Then, below
this, we'll use console.log to print the title, note.title:
// note console.log(typeof note); console.log(note.title);
Now, over in Terminal, you can see (refer to the following screenshot) that I have saved the file in a broken
state and it crashed, and that's expected when you're using nodemon:
To resolve this, the first thing I'll do is fill out the originalNoteString variable, which we had commented out
earlier. It will now be a variable called
originalNoteString, and we'll set it equal to the return value from JSON.stringify.
Now, we know JSON.stringify takes our regular object and it converts the object into a string. In this case,
we'll take the originalNote object and convert it into a string. The next line, which we already have filled
out, will save that JSON value into the notes.JSON file. Then we will read that value out:
var originalNoteString = JSON.stringify(originalNote);
The next step will be to create the note variable. The note variable will be set equal to JSON.parse.
The JSON.parse method takes the string JSON and converts it back into a regular JavaScript object or array,
depending on whatever you save. Here we will pass in noteString, which we'll get from the file:
var note = JSON.parse(noteString);
With this in place, we are now done. When I save this file, nodemon will automatically restart and we
would expect to not see an error. Instead, we expect that we'll see the object type as well as the note title.
Right inside Terminal, we have object and Some title printing to the screen:
With this in place, we've successfully completed the challenge. This is exactly how we will save our notes.
When someone adds a new note, we'll use the following code to save it:
var originalNote = { title: 'Some title', body: 'Some body'
};
var originalNoteString = JSON.stringify(originalNote); fs.writeFileSync('notes.json', originalNoteString);
When someone wants to read their note, we'll use the following code to read it:
var noteString = fs.readFileSync('notes.json'); var note = JSON.parse(noteString); console.log(typeof note);
console.log(note.title);
Now, what if someone wants to add a note? This will require us to first read all of the notes, then modify
the notes array, and then use the code (refer to the previous code block) to save the new array back into
the filesystem.
If you open up that notes.JSON file, you can see right here that we have our JSON code inside the file:
.json is actually a file format that's supported by most text editors, so I actually already have some nice
syntax highlighting built in. Now, in the next section, we'll be filling out the addNote function using the
exact same logic that we just used inside of this section.
Adding and saving notes
In the previous section, you learned how to work with JSON inside Node.js, and this is the exact format
we'll be using for the notes.js application. When you first run a command, we'll load in all the notes that
might already exist. Then we'll run the command, whether it's adding, removing, or reading notes. Finally,
if we've updated the array, like we will when we add and remove notes, we'll save those new notes back
into the JSON file.
Now, this will all happen inside of the addNote function, which we defined in the notes.js application, and
we already wired up this function. In earlier sections, we ran the app add command, and this function
executed with the title and body arguments.
Adding notes
To get started with adding notes, the first thing we'll do is create a variable called notes, and for the
moment, we'll set it equal to an empty array, just as in the following, using our square brackets:
var addNote = (title, body) => { var notes = [];
};
Now that we have the empty array, we can go ahead and make a variable called
note, which is the individual note. This will represent the new note:
var addNote = (title, body) => { var notes = [];
var note = {
}
};
On that note, we'll have the two properties: a title and a body. Now, title can be set equal to the title
variable, but, as we know, inside ES6, we can simply remove it when both values are the same; so we'll add
title and body as shown here:
var addNote = (title, body) => { var notes = [];
var note = { title, body
};
};
Now we have the note and the notes array.
Adding notes to the notes array
The next step in the process of adding notes will be to add the note to the notes array. The notes.push
method will let us do just that. The push method on an array lets you pass in an item, which gets added to
the end of the array, and in this case, we'll pass in the note object. So we have an empty array, and we add
our one item, as shown in the following code; next, we push it in, which means that we have an array with
one item:
var addNote = (title, body) => { var notes = [];
var note = { title, body
};
notes.push(note);
};
The next step in the process will be to update the file. Now, we don't have a file in place, but we can load
an fs function and start creating the file.
Up above the addNote function, let's load in the fs module. I'll create a const variable called fs and set it
equal to the return result from require, and we'll require the fs module, which is a core node module, so
there's no need to install it using NPM:
const fs = require('fs');
With this in place, we can take advantage of fs inside the addNote function.
Right after we push our item on to the notes array, we'll call fs.writeFileSync, which we've used before. We
know we need to pass in two things: the file name and the content we want to save. For the file, I'll call,
notes-data.JSON, and then we'll pass in the content to save, which in this case will be the stringify notes
array, which means we can call JSON.stringify passing in notes:
notes.push(note);
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
We could have broken JSON.stringfy(notes) out into its own variable
and referenced the variable in the above statement, but since we'll only be using it in one place, I
find this is the better solution.
At this point, when we add a new note, it will update the notes-data.JSON file, which will be created on the
machine since it does not exist, and the note will sit inside it. Now, it's important to note that currently
every time you add a new note, it will wipe all existing ones because we never load in the existing ones,
but we can get started testing that this note works as expected.
I'll save the file, and over inside of Terminal, we can run this file using node app.js. Since we want to add a
note, we will be using that add command which we set up, then we'll specify our title and our body. The
title flag can get set equal to secret, and for the body flag, I'll set it equal to the Some body here string, as
shown here:
node app.js add --title=secret --body="Some body here"
Now, when we run this command from Terminal, we'll see what we'd expect:
As shown in the preceding screenshot, we see a couple of the file commands we added: we see that the
add command was executed, and we have our Yargs arguments. The title and body arguments also show
up. Inside Atom, we also see that we have a new notes-data.json file, and in the following screenshot, we
have our note, with the secret title and the Some body here body:
This is the first step in wiring up that addNote function. We have an existing notes file and we do want to
take advantage of these notes. If notes already exist, we don't want to simply wipe them every time
someone adds a new note. This means that in notes.js, earlier at the beginning of the addNote function,
we'll fetch those notes.
Fetching new notes
I'll add code for fetching new notes where I define the notes and note variables. As shown in the following
code, we'll use fs.readFileSync, which we've already explored. This will take the filename, in our case,
notes-data.JSON. Now, we will want to store the return value from readFileSync on a variable; I'll call that
variable, notesString:
var notesString = fs.readFileSync('notes-data.json');
Since this is the string version, we haven't passed it through the JSON.parse method. So, I can set notes
(the variable we defined earlier in addNote function) equal to the return value from the JSON.parse
method. Then JSON.parse will take the string from the file we read and it will parse it into an array; we
could pass in notesString just like this:
notes = JSON.parse(notesString);
With this in place, adding a new note is no longer going to remove all of the notes that were already there.
Over in Terminal, I'll use the up arrow key to load in the last command, and I'll navigate over to the title
flag and change it to secret2 and rerun the command:
node app.js add --title=secret2 --body="Some body here"
In Atom, this time you can see we now have two notes inside of our file:
We have an array with two objects; the first one has the title of secret and the second one has the title of
secret2, which is brilliant!
Trying and catching code block
Now, if the notes-data.json file does not exist, which it won't when the user first runs the command, the
program will crash, as shown in the following code output. We can prove this by simply rerunning the last
command after deleting the note-data.JSON file:
Right here, you can see we're actually getting a JavaScript error, no such file or directory; it's trying to open
up the notes-data.JSON file, but without much success. To fix this, we'll use a try-catch statement from
JavaScript, which hopefully you've seen in the past. To brush up this, let's go over it really quick.
To create a try-catch statement, all you do is you type try, which is a reserved keyword, and then you open
and close a set of curly braces. Inside the curly braces is the code that will run. This is the code that may or
may not throw an error. Next, you'll specify the catch block. Now, the catch block will take an argument,
an error argument, and it also has a code block that runs:
try{
} catch (e) {
}
This code will run if and only if one of your errors in try actually occurs. So, if we load the file using
readFileSync and the file exists, that's fine, catch block will never run. If it fails, catch block will run and we
can do something to recover from that error. With this in place, all we'll do is move the noteString variable
and the JSON.parse statements into try, as shown here:
try{
var notesString = fs.readFileSync('notes-data.json'); notes = JSON.parse(notesString);
} catch (e) {
}
That's it; nothing else needs to happen. We don't need to put any code in catch, although you do need to
define the catch block. Now, let's take a look at what happens when we run the whole code.
The first thing that happens is that we create our static variables—nothing special there—then we try to
load in the file. If the notesString function fails, that is fine because we already defined notes to be an
empty array. If the file doesn't exist and it fails, then we probably want an empty array for notes anyways,
because clearly there are no notes, and there's no file.
Next up, we'll parse that data into notes. There is a chance that this will fail if there's invalid data in the
notes-data.JSON file, so the two lines can have problems. By putting them in try-catch, we're basically
guaranteeing that the program isn't going to work unexpectedly, whether the file does or doesn't exist, but
it contains corrupted data.
With this in place, we can now save notes and rerun that previous command. Note that I do not have the
notes-data file in place. When I run the command, we
don't see any errors, everything seems to run as expected:
When you now visit Atom, you can see that the notes-data file does indeed exist, and the data inside it
looks great:
This is all we need to do to fetch the notes, update the notes with the new note, and finally save the notes
to the screen.
Now, there is still a slight problem with addNote. Currently, addNote allows for duplicate titles; I could
already have a note in the JSON file with the title of secret. I can come along and try to add a new note
with the title of secret and it will not throw an error. What I'd like to do is to make the title unique, so that
if there's already a note with that title, it will throw an error, letting you know that you need to create a
note with a different title.
Making the title unique
The first step to make the title unique will be to loop through all of the notes after we load them in and
check whether there are any duplicates. If there are duplicates, we'll not call the following two lines:
notes.push(note);
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
If there are no duplicates then it's fine, we will call both of the lines shown in the preceding code block,
updating the notes-data file.
Now, we'll be refactoring this function down the line. Things are getting a little wonky and a little out of
control, but for the moment, we can add this functionality right into the function. Let's go ahead and make
a variable called
duplicateNotes.
The duplicateNotes variable will eventually store an array with all of the notes that already exist inside the
notes array that have the title of the note you're trying to create. Now, this means that if the
duplicateNotes array has any items, that's bad. This means that the note already exists and we should not
add the note. The duplicateNotes variable will get set equal to a call to notes, which is our array of
notes.filter:
var duplicateNotes = notes.filter();
The filter method is an array method that takes a callback. We'll use an arrow function, and that callback
will get called with the argument. In this case, it will be the singular version; if I have an array of notes, it
will be called with an individual note:
var duplicateNotes = notes.filter((note) => {
});
This function gets called once for every item in the array, and you have the opportunity to return either
true or false. If you return true, it will keep that item in the array, which will eventually get saved into
duplicateNotes. If you return false, the new array it generates will not have that item inside duplicateNotes
variable. All we want to do is to return true if the titles match, which means that we can return note.title
=== title, as shown here:
var duplicateNotes = notes.filter((note) => { return note.title === title;
});
If the titles are equal, then the preceding return statement will result as true and the item will be kept in
the array, which means that there are duplicate notes. If the titles are not equal, which is most likely the
case, the statement will result as false, which means that there are no duplicate notes. Now, we can
simplify this a little more using arrow functions.
Arrow functions actually allow you to remove the curly braces if you only have one statement.
I'll use the arrow function, as shown here:
var duplicateNotes = notes.filter((note) => note.title === title);
Here, I have deleted everything except note.title === title and added this in front of the arrow function
syntax.
This is perfectly valid using ES6 arrow functions. You have your arguments on the left, the arrow, and on
the right, you have one expression. The expression doesn't take a semicolon and it's automatically
returned as the function result. This means that the code we have here is identical to the code we had
earlier, only it's much simpler and it only takes up one line.
Now that we have this in place, we can go ahead and check the length of the duplicateNotes variable. If
the length of duplicateNotes is greater than 0, this means that we don't want to save the note because a
note already exists with that title. If it is 0, we'll save the note.
if(duplicateNotes.length === 0) {
}
Here, inside the if condition, we're comparing the notes length with the number zero. If they are equal,
then we do want to push the note onto the notes array and save the file. I'll cut the following two lines:
notes.push(note);
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
Let's paste them right inside of the if statement, as shown here:
if(duplicateNotes.length === 0) { notes.push(note);
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
}
If they're not equal, that's okay too; in that case we'll do nothing.
With this in place, we can now save our file and test this functionality out. We have our notes-data.json
file, and this file already has a note with a title of secret2. Let's rerun the previous command to try to add a
new note with that same title:
node app.js add --title=secret2 --body="Some body here"
You're in Terminal, so we'll head back into our JSON file. You can see right here that we still just have one
note:
Now all the titles inside of our application will be unique, so we can use these titles to fetch and delete
notes.
Let's go ahead and test that other notes can still be added. I'll change the title
flag from secret2 to secret, and run that command:
node app.js add --title=secret --body="Some body here"
Inside our notes-data file, you can see both notes show up:
As I mentioned earlier, next we will be doing some refactoring, since the code that loads the file, and the
code that saves the file, will both be used in most of the functions we have defined and/or will define (that
is, the getAll, getNote and removeNote functions).
Refactoring
In the previous section, you created the addNote function, which works well. It starts by creating some
static variables, then we fetch any existing notes, we check for duplicates, and if there are none, we push it
onto the list, and then we save the data back into the filesystem.
The only problem is that we'll be doing a lot of these steps over and over again for every method. For
example, with getAll, the idea is to fetch all of the notes, and send them back to app.js so it can print them
to the screen for the user. The first thing we'll to do inside of the getAll statement is have the same code;
we'll have our try-catch block to fetch the existing notes.
Now, this is a problem because we'll be repeating code throughout the application. It will be best to break
out the fetching of notes and the saving of notes into separate functions that we can call in multiple
locations.
Moving functionality into individual functions
To resolve the problem, I'd like to get started by creating two new functions:
fetchNotes saveNotes
The first function, fetchNotes, will be an arrow function, and it will not to take any arguments since it will
be fetching notes from the filesystem, as shown here:
var fetchNotes = () => {
};
The second function, saveNotes, will need to take an argument. It will need to take the notes array you
want to save to the filesystem. We'll set it equal to an arrow function, and then we'll provide our
argument, which I will name notes, as shown here:
var saveNotes = (notes) => {
};
Now that we have these two functions, we can go ahead and start moving some of the functionality from
addNote up into the individual functions.
Working with fetchNotes
First up, let's do fetchNotes, which will need the following try-catch block.
I'll actually cut it out of addNote and paste it in the fetchNotes function, as shown here:
var fetchNotes = () => { try{
var notesString = fs.readFileSync('notes-data.json'); notes = JSON.parse(notesString);
} catch (e) {
}
};
This alone is not enough, because currently we don't return anything from the function. What we want to
do is to return the notes. This means that instead of saving the result from JSON.parse onto the notes
variable, which we haven't defined, we'll simply return it to the calling function, as shown here:
var fetchNotes = () => { try{
var notesString = fs.readFileSync('notes-data.json');
return JSON.parse(notesString);
} catch (e) {
}
};
So, if I call fetchNotes in the addNote function, shown as follows, I will get the notes
array because of the return statement in the preceding code.
Now, if there are no notes, maybe there's no file at all; or there is a file, but the data isn't JSON, we can
return an empty array. We'll add a return statement inside of catch, as shown in the following code block,
because remember, catch runs if anything inside try fails:
var fetchNotes = () => { try{
var notesString = fs.readFileSync('notes-data.json'); return JSON.parse(notesString);
} catch (e) {
return [];
}
};
Now, this lets us simplify addNote even further. We can remove the empty space and we can take the
array that we set on the notes variable and remove it and instead call fetchNotes, as shown here:
var addNote = (title, body) => {
var notes = fetchNotes();
var note = {
title, body
};
With this in place, we now have the exact same functionality we had before, but we have a reusable
function, fetchNotes, which we can use in the addNote function to handle the other commands that our
app will support.
Instead of copying code and having it in multiple places in your file, we've broken it into one place. If we
ever want to change how this functionality works, whether we want to change the filename or some of the
logic such as the try-catch block, we can change it once instead of having to change it in every function we
have.
Working with saveNotes
Now, the same thing will go for saveNotes just as in the case of the fetchNotes function. The saveNotes
function will take the notes variable and it will say this using fs.writeFileSync. I will cut out the line in
addNote that does this (that is,
fs.writeFileSync('notes-data.json', JSON.stringfy(notes));) and paste it in the saveNotes
function, as shown here:
var saveNotes = (notes) => {
fs.writeFileSync('notes-data.json', JSON.stringify(notes));
};
Now, saveNotes doesn't need to return anything. In this case, we'll copy the line in saveNotes and then call
saveNotes in the if statement of the addNote function, as shown in the following code:
if (duplicateNotes.length === 0) { notes.push(note);
saveNotes();
}
This might seem like overkill, we've essentially taken one line and replaced it with a different line, but it is
a good idea to start getting in the habit of creating reusable functions.
Now, calling saveNotes with no data is not going to work, we want to pass in the
notes variable, which is our notes array defined earlier in the saveNotes function:
if (duplicateNotes.length === 0) { notes.push(note); saveNotes(notes);
}
With this in place, the addNote function should now work as it did before we did any of our refactoring.
Testing the functionality
The next step in the process will be to test this out by creating a new note. We already have two notes,
with a title of secret and a title of secret2 in notes-data.json, let's make a third one using the node app.js
command in Terminal. We'll use the add command and pass in a title of to buy and a body of food, as
shown here:
node app.js add --title="to buy" --body="food"
This should create a new note, and if I run the command, you can see we don't have any obvious errors:
Inside of our notes-data.json file, if I scroll to the right, we have our brand new note as a title of to buy and
a body of food:
So, everything is working as expected even though we've refactored the code. Now, the next thing I want
to do inside addNote is take a moment to return the note that's being added, and that will happen right
after saveNotes comes back. So we'll return note:
if (duplicateNotes.length === 0) { notes.push(note); saveNotes(notes);
return note;
}
This note object will get returned to whoever called the function, and in this case, it will get returned to
app.js, where we called it in the if else block of the add command in the app.js file. We can make a variable
to store this result and we can call it note:
if (command === 'add')
var note = notes.addNote(argv.title, argv.body);
If note exists, then we know that the note was created. This means that we can go ahead and print a
message, like Note created, and we can print the note title and the note body. Now, if note does not exist,
if it's undefined, this means that there was a duplicate and that title already exists. If that's the case, I want
you to print an error message such as Note title already in use.
There's a ton of different ways you could do this. The goal, though, is to print two different messages
depending on whether or not a note was returned.
Now, inside addNote, if the duplicateNotes if statement never runs, we don't have an explicit call to
return. But as you know, in JavaScript, if you don't call return, then undefined automatically is returned.
This means that if duplicateNotes.length is not equal to zero, undefined will be returned and we can use
that as the condition for our statement.
The first thing I'll do here is to create an if statement, right next to the note
variable we defined in app.js:
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body); if (note) {
}
This will be an object if things went well, and it will be undefined if things went poorly. This code in here is
only ever going to run if it's an object. The Undefined result will fail the condition inside of JavaScript.
Now, if the note was created successfully, what we'll do is to print a little message to the screen, using the
following console.log statement:
if (note) {
console.log('Note created');
}
If things went poorly, inside the else clause, we can call console.log, and we can print something like Note
title taken, as shown here:
if (note) {
console.log('Note created');
} else {
console.log('Note title taken');
}
Now, the other thing that we want to do if things went well is print the notes content. I'll do this by first
using console.log to print a couple of hyphens. This will create a little space above my note. Then I can use
console.log twice: the first time we'll print the title, I'll add Title: as a string to show you what exactly
you're seeing, then I can concatenate the title, which we have access to in note.title, as shown in this code:
if (note) {
console.log('Note created'); console.log('--'); console.log('Title: ' + note.title);
Now, the preceding syntax uses an ES5 syntax; we can swap this out with an ES6 syntax using what we've
already talked about: template strings. We'll add Title, a colon, and then we can use our dollar sign with
our curly braces to inject the note.title variable, as shown here:
console.log(`Title: ${note.title}`);
Similarly, I'll add note.body after this to print out the body of the note. With this in place, the code should
look like:
if (command === 'add') {
var note = note.addNote(argv.title, argv.body); if (note) {
console.log('Note created'); console.log('--'); console.log(`Title: ${note.title}`); console.log(`Body:
${note.body}`);
} else {
console.log('Note title taken');
}
Now, we should be able to run our app and see both of the title and body notes printed. In Terminal, I'll
rerun the previous command. This will try to create a note with to buy, which already exists, so we should
get an error message, and right here you can see Note title taken:
Now, we can rerun the command, changing the title to something else, such as to buy from store. This is a
unique note title so the note should get created without any problems:
node app.js add --title="to buy from store" --body="food"
As shown in the preceding output, you can see that we get just that: we have our Note created message,
our little spacer, and our title along with the body.
The addNote command is now complete. We have an output when the command actually finishes, and we
have all the code that runs behind the scenes to add the note to the data that gets stored in our file.
Summary
In this chapter, you learned that parsing in process.argv can be a real pain. We would have to write a lot of
manual code to parse out those hyphens, the equal signs, and the optional quotes. However, yargs can do
all of that for us and it puts it on a really simple object we can access. You also learned how to work with
JSON inside Node.js.
Next, we filled out the addNote function. We're able to add notes using the command line, and we're able
to save those notes into a JSON file. Finally, we pulled out a lot of the code from addNote into separate
functions, fetchNotes and saveNotes, which are now separate, and they're able to be reused throughout
the code. When we start filling out the other methods, we can simply call fetchNotes and saveNotes
instead of having to copy the contents over and over again to every new method.
In the next chapter, we'll continue our journey on node fundamentals. We'll explore some more concepts
related to node, such as debugging; we'll work on the read and remove notes commands. Apart from this,
we'll also learn about the advanced features of yargs and the arrow function.
Node Fundamentals – Part 3
We start adding support for all the other commands inside of the notes application. We'll take a look at
how we can create our read command. The read command will be responsible for fetching the body of an
individual note. It will fetch all the notes and print them to the screen. Now, aside from all of that, we'll be
looking at debugging broken apps, and we'll look at some new ES6 features. You'll learn how to use the
built-in Node debugger.
Then, you will learn a little bit more about how we can configure yargs for the command-line interface
applications. We'll learn how to set up the commands, their descriptions, and the arguments. We'll be able
to set various properties on the arguments, for example, whether or not they're required, and others.
Removing a note
In this section, you will write the code for removing a note when someone uses that remove command,
and they pass in the title of the note they want to remove. In the previous chapter, we already created
some utility functions that help us with fetching and saving notes, so the code should actually be pretty
simple.
Using the removeNote function
The first step in the process is to fill out the removeNote function, which we defined in the previous
chapters, and this will be your challenge. Let's remove console.log from the removeNote function in the
notes.js file. You only need to write three lines of code to get this done.
Now, the first line will fetch the notes, then the job will be to filter out the notes, removing the one with
title of argument. That means we want to go through all of the notes in the notes array, and if any of them
have a title that matches the title we want to remove, we want to get rid of them. And this can be done
using the notes.filter function we used earlier. All we have to do is switch the equality statement in the
duplicateNotes function from equals to not equals, and this code will do just that.
It will go through the notes array. Every time it finds a note that doesn't match the title it will keep it,
which is what we want, and if it does find the title it will return false and remove it from the array. And
then we will add the third line, which is to save the new notes array:
var removeNote = (title) => {
// fetch notes
// filter notes, removing the one with title of argument
// save new notes array
};
The preceding code lines are the only three lines you need to fill out. Don't worry about returning anything
from removeNote or filling out anything inside of
app.js.
The first thing we will do for the fetchNotes line is to create a variable called notes, just like we did in
addNote in the previous chapter, and we'll set it equal to the return result from fetchNotes:
var removeNote = (title) => {
var notes = fetchNotes();
// filter notes, removing the one with title of argument
// save new notes array
};
At this point our notes variable stores an array of all of the notes. The next thing we need to do is filter our
notes.
If there is a note that has this title, we want to remove it. This will be done by creating a new variable, and
I'll call this one filteredNotes. Here we'll set filteredNotes equal to the result that will come back from
notes.filter, which we already used up previously:
var removeNote = (title) => { var notes = fetchNotes();
// filter notes, removing the one with title of argument
var filteredNotes = notes.filter();
// save new notes array
};
We know that notes.filter takes a function as its one and only argument, and that function gets called with
the individual item in the array. In this case it would be a note. And we can do this all on one line using the
ES6 arrow syntax.
If we have only one statement, we don't need to open and close curly braces.
That means right here we can return true if note.title does not equal the title that's passed into the
function:
var removeNote = (title) => { var notes = fetchNotes();
var filteredNotes = notes.filter((note) => note.title !== title);
// save new notes array
};
This will populate filteredNotes with all of the notes whose titles do not match the one passed in. If the
title does match the title passed in, it will not be added to filteredNotes because of our filter function.
The last thing to do is to call saveNotes. Right here, we'll call saveNotes passing in the new notes array
which we have under the filteredNotes variable:
var removeNote = (title) => { var notes = fetchNotes();
var filteredNotes = notes.filter((note) => note.title !== title);
saveNotes(filteredNotes);
// save new notes array
};
If we were to pass in notes, it wouldn't work as expected; we're filtering the notes out but we're not
actually saving those notes, so it will not get removed from the JSON. We need to pass filteredNotes as
shown in the preceding code. And we can test these by saving the file and trying to remove one of our
notes.
I'll try to remove secret2 from the notes-data.json file. That means all we need to do is run the command,
which we called remove, that is specified over in app.js, (refer to the following code image, and then it will
call our function).
I'll run Node with app.js, and we'll pass in the remove command. The only argument we need to provide
for remove is the title; there's no need to provide the body. I'll set this equal to secret2:
node app.js remove --title=secret2
As shown in the screenshot, if I hit enter you can see we don't get any output. Although we do have the
command remove printing, there is no message saying whether or not a note was removed, but we'll add
that later in the section.
For now, we can check the data. And right here you can see secret2 is nowhere in sight:
This means our remove method is indeed working as expected. It removed the note whose title matched
and it kept all the notes whose title was not equal to secret2, exactly what we wanted.
Printing a message of removing notes
Now, the next thing we'll do is print a message depending on whether or not a note was actually removed.
That means app.js, which calls the removeNote function, will need to know whether or not a note was
removed. And how do we figure that out? How can we possibly return that given the information we have
in notes.js removeNotes function?
Well, we can, because we have two really important pieces of information. We have the length of the
original notes array and we have the length of the new notes array. If they're equal then we can assume
that no note was removed. If they are not equal, we'll assume that a note was removed. And that is exactly
what we'll do.
If the removeNote function returns true, that means a note was removed; if it returns false, that means a
note was not removed. In the removeNotes function we can add return, as shown in the following code.
We'll check if notes.length does not equal
filteredNotes.length:
var removeNote = (title) => { var notes = fetchNotes();
var filteredNotes = notes.filter((note) => note.title !== title); saveNotes(filteredNotes);
return notes.length !== filteredNotes.length;
};
If they're not equal it will return true, which is what we want because a note was removed. If they're equal
it will return false, which is great.
Now, inside of app.js we can add a few lines in the removeNote, else if block to make the output for this
command a little nicer. The first thing to do is to store that Boolean. I'll make a variable called
noteRemoved and we'll set that equal to the return, result as shown in the following code, which will
either be true or false:
} else if (command == 'remove') {
var noteRemoved = notes.removeNote(argv.title);
}
On the next line, we can create our message, and I'll do this all on one line using the ternary operator.
Now, the ternary operator lets you specify a condition. In our case, we'll use a var message and it will be
set equal to the condition noteRemoved, which will be true if a note was removed and false if it wasn't.
Now, the ternary operator can be a little confusing, but it's really useful inside JavaScript and Node.js. The
format for the ternary operator is first we add the condition, question mark, the truthy expression to run,
colon, and then the falsy expression to run.
After the condition, we'll put a space with a question mark and a space; this is the statement that will run if
it's true. If the noteRemoved condition passes, what we want to do is set message equal to Note was
removed:
var message = noteRemoved ? 'Note was removed' :
Now, if noteRemoved is false, we can specify that condition right after the colon in the previous statement.
Here, if there is no note removed we'll use the text Note not found:
var message = noteRemoved ? 'Note was removed' : 'Note not found';
Now with this in place, we can test out our message. The last thing to do is print the message to the screen
using console.log passing in message:
var noteRemoved = notes.removeNote(argv.title);
var message = noteRemoved ? 'Note was removed' : 'Note not found'; console.log(message);
This lets us avoid if statements that make our else-if clause to remove unnecessarily complex.
Back inside of Atom we can rerun the last command, and in this case no note will get removed because we
already deleted it. And when I run it, you can see that Note not found prints to the screen:
Now I'll remove a note that does exist; in notes-data.json I have a note with a title of secret as shown here:
Let's rerun the command removing the 2 from the title in Terminal. When I run this command, you can see
Note was removed prints to the screen:
That is it for this section; we now have our remove command in place.
Reading note
In this section, you will be responsible for filling out the rest of the read command. Now, the read
command does have an else-if block to find in app.js where we call getNote:
} else if (command === 'read') { notes.getNote(argv.title);
getNote is defined over inside notes.js, even though currently it just prints out some dummy text:
var getNote = (title) => { console.log('Getting note', title);
};
What you'll need to do in this section is wire up both of these functions.
First up, you will need to do something with the return value from getNote. Our getNote function will
return the note object if it finds it. If it doesn't, it will return undefined just like we do for addNote
discussed in the section Adding and saving note, in the previous chapter.
After you store that value, you'll do some printing using console.log, similar to what we have here:
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body); if (note) {
console.log('Note created'); console.log('--'); console.log(`Title: ${note.title}`); console.log(`Body:
${note.body}`);
} else {
console.log('Note title taken');
}
Obviously, Note created will be something like Note read and Note title taken will be something like Note
not found, but the general flow is going to be exactly the same. Now, once you have that wired up inside
of app.js, you can move on to notes.js, filling out the function.
Now, the function inside of notes.js isn't going to be that complex. All you need
to do is fetch the notes, like we've done in previous methods, then you're going to use notes.filter, which
we explored to only return notes whose title matches the title passed in as the argument. Now, in our case
this is either going to be zero notes, which means the note is not found, or it's going to be one note, which
means we've found the note that the person wants to return.
Next, we do need to return that note. It's important to remember the return value from notes.filter is
always going to be an array, even if that array only has one item. What you're going to need to do is return
the first item in the array. If that item doesn't exist that's fine, it'll return undefined, as we want. If it does
exist, great, that means we found the note. This method only requires three lines of code, one for fetching,
one for filtering, and the return statement. Now, once you have all that done we'll test it out.
Using the getNote function
Let's work on this method. Now, the first thing I'll do is fill out, inside of app.js, a variable called note which
is going to store the return value from getNote:
} else if (command === 'read') {
var note = notes.getNote(argv.title);
Now, this could be an individual note object or it could be undefined. In the next line, I can use an if
statement to print the message if it exists, or if it does not exist. I'll use if note, and I am going to attach an
else clause:
} else if (command === 'read') {
var note = notes.getNote(argv.title); if (note) {
} else {
}
This else clause will be responsible for printing an error if the note is not found. Let's get started with that
first since it's pretty simple, console.log, Note not found, as shown here:
if (note) {
} else {
console.log('Note not found');
}
Now that we have our else clause filled out we can fill out the if statement. For this, I'll print a little
message, console.log ('Note found') will get the job done. Then we can move on to printing the actual note
details, and we already have that code in place. We are going to add the hyphenated spacer, then we have
our note title and our note body as shown here:
if (note) {
console.log('Note found'); console.log('--'); console.log(`Title: ${note.title}`); console.log(`Body:
${note.body}`);
} else {
console.log('Note not found');
}
Now that we're done with the inside of app.js, we can move into the notes.js file and fill out the getNote
method because currently it doesn't do anything with the title that gets passed in.
Inside notes, what you needed to do was fill out those three lines. The first one is going to be responsible
for fetching the notes. We already have did that before with the fetchNotes function in the previous
section:
var getNote = (title) => { var notes = fetchNotes();
};
Now that we have our notes in place we, can call notes.filter, returning all of the notes. I'll make a variable
called filteredNotes, setting it equal to notes.filter. Now, we know that the filter method takes a function,
I'll define an arrow function (=>) just like this:
var filteredNotes = notes.filter(() => {
});
Inside the arrow function (=>), we'll get the individual note passed in, and we'll return true when the note
title, the title of the note we found in our JSON file, equals, using triple equals, title:
var filteredNotes = notes.filter(() => { return note.title === title;
});
};
This will return true when the note title matches and false if it doesn't. Alternatively, we can use arrow
functions, and we only have one line, as shown following, where we return something; we can cut out our
condition, remove the curly braces, and simply paste that condition right here:
var filteredNotes = notes.filter((note) => note.title === title);
This has the exact same functionality, only it's a lot shorter and easier to look at.
Now that we have all of the data, all we need to do is return something, and we'll return the first item in
the filteredNotes array. Next, we'll grab the first item, which is the index of zero, and then we just need to
return it using the return keyword:
var getNote = (title) => { var notes = fetchNotes();
var filteredNotes = notes.filter((note) => note.title === title); return filteredNotes[0];
};
Now, there is a chance that filteredNotes, the first item, doesn't exist, and that's fine, it's going to return
undefined, in which case our else clause will run, printing Note not found. If there is a note, great, that's
the note we want to print, and over in app.js we do just that.
Running the getNote function
Now that we have this in place we can test out this brand new functionality inside of Terminal by running
our app using node app.js. I'll use the read command, and I'll pass in a title equal to some string that I
know does not exist inside of a title in the notes-data.json file:
node app.js read --title="something here"
When I run the command, we get Note not found, as shown here, and this is exactly what we want:
Now, if I do try to fetch a note where the title does exist, I would expect that note to come back.
In the data file I have a note with a title of to buy; let's try to fetch that one. I'll use the up arrow key to
populate the previous command and replace the title with to space, buy, and hit enter:
As shown in the previous code, you can see Note found prints to the screen, which is fantastic. Following
Note found we have our spacers and following that we have the title, which is to buy, and the body, which
is food, exactly as it appears inside of the data file. With this in place, we are done with the read command.
The DRY principle
Now, there is one more thing I want to tackle before we wrap up this section. Inside app.js we now have
the same code in two places. We have the space or title body in the add command as well as in the read
command:
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body); if (note) {
console.log('Note created'); console.log('--'); console.log(`Title: ${note.title}`); console.log(`Body:
${note.body}`);
} else {
console.log('Note title taken');
}
} else if (command === 'list') { notes.getAll();
} else if (command === 'read') {
var note = notes.getNote(argv.title); if (note) {
console.log('Note found'); console.log('--'); console.log(`Title: ${note.title}`); console.log(`Body:
${note.body}`);
} else {
console.log('Note not found');
}
When you find yourself copying and pasting code, it's probably best to break that out into a function that
both locations call. This is the DRY principle, which stands for Don't Repeat Yourself.
Using the logNote function
In our case, we are repeating ourselves. It would be best to break this out into a function that we can call
from both places. In order to do this, all we're going to do is make a function in notes.js called logNote.
Now, in notes.js, down following the removeNote function, we can make that brand new function a
variable called logNote. This is going to be a function that takes one argument. This argument will be the
note object because we want to print both the title and the body. As shown here, we'll expect the note to
get passed in:
var logNote = (note) => {
};
Now, filling out the logNote function is going to be really simple, especially when you're solving a DRY
issue, because you can simply take the code that's repeated, cut it out, and paste it right inside the logNote
function. In this case the variable names line up already, so there is no need to change anything:
var logNote = (note) => { console.log('--'); console.log(`Title: ${note.title}`); console.log(`Body:
${note.body}`);
};
Now that we have the logNote function in place, we can change things over in app.js. In app.js, where we
have removed the console.log statements we can call notes.logNote, passing in the note object just like
this:
else if (command === 'read') {
var note = notes.getNote(argv.title); if (note) {
console.log('Note found');
notes.logNote(note);
} else {
console.log('Note not found');
}
And we can do the same thing in case of the add command if block. I can remove these three console.log
statements and call notes.logNote, passing in note:
if (command === 'add') {
var note = notes.addNote(argv.title, argv.body);
if (note) {
console.log('Note created');
notes.logNote(note);
} else {
console.log('Note title taken');
}
And now that we have this in place, we can rerun our program and hopefully what we see is the exact
same functionality.
The last thing to do before we rerun the program is export the logNote function in exports module in
notes.js file. LogNote is going to get exported and we're using the ES6 syntax to do that:
module.exports = { addNote, getAll, getNote, removeNote, logNote
};
With this in place, I can now rerun the previous command from Terminal using up and hit enter:
node app.js read --title="to buy"
As shown, we get Note found printing to the screen, with the title and the body just like we had before. I'm
also going to test out the add command to make sure that one's working, node app.js add; we will use a
title of things to do and a body of go to post office:
node app.js add --title="things to do" --body="go to post office"
Now, when I hit enter, we would expect the same log to print as it did before for the add command, and
that's exactly what we get:
Note created prints, we get our spacer, and then we get our title and our body.
In the next section, we're going to cover one of the most important topics in the book; which is debugging.
Knowing how to properly debug programs is going to save you literally hundreds of hours over your
Node.js career. Debugging can be really painful if you don't have the right tools, but once you know how
it's done, it really isn't that bad and it can save you a ton of time.
Debugging
In this section, we're going to use the built-in debugger, which can look a little complex because it's run
inside of the command line. That means that you have to use the command-line interface, which is not
always the most pleasant thing to look at. In the next section, though, we are going to be installing a third-
party tool that uses Chrome DevTools in order to debug your Node app. That one looks great because the
Chrome DevTools are fantastic.
Executing a program in debug mode
Before going ahead, we will learn that we do need to create a place to play around with debugging and
that's going to happen in a playground file, since the code we're going to write is not going to be important
to the notes app itself. Inside the notes app I'll make a new file called debugging.js:
In debugging.js we're going to start off with a basic example. We're going to make an object called person,
and on that object for the moment, we're going to set one property name. Set it equal to your name, I'll
set mine equal to the string Andrew as
shown:
var person = { name: 'Andrew'
};
Next up we're going to set another property, but in the next line, person.age. I'll set mine equal to my age,
25:
var person = { name: 'Andrew'
};
person.age = 25;
Then we're going to add another statement that changes the name, person.name
equals something like Mike:
var person = { name: 'Andrew'
};
person.age = 25; person.name = 'Mike';
Finally, we're going to console.log the person object, the code is going to look like this:
var person = { name: 'Andrew'
};
person.age = 25; person.name = 'Mike'; console.log(person);
Now, we actually already have a form of debugging in this example, we have a
console.log statement.
As you're going through the Node application development process, you may or may not have used
console.log to debug your app. Maybe something's not working as expected and you want to figure out
exactly what that variable has stored inside of it. For example, if you have a function that solves a math
problem, maybe at one part in the function the equation is wrong and you're getting a different result.
Using console.log can be a pretty great way to do that, but it's super limited. We can view that by running
it from Terminal, I'll run the following command for this:
node playground/debugging.js
When I run the file, I do get my object printed out to the screen, which is great, but, as you know, if you
want to debug something besides the person object you have to add another console.log statement in
order to do that.
Imagine you have something like our app.js file, you want to see what command equals, then you want to
see what argv equals, it could take a lot of time to add and remove those console.log statements. There is
a better way to debug. This is using the Node debugger. Now, before we make any changes to the project,
we'll take a look at how the debugger works inside of Terminal, and as I warned you in the beginning of the
section, the built-in Node debugger, while it is effective, is a little ugly and hard to use.
For now, though, we are going to run the app much the same way, only this time we're going to type node
inspect. Node debug is going to run our app completely differently from the regular Node command.
We're running the same file in the playground folder, it's called debugging.js:
node inspect playground/debugging.js
When you hit enter, you should see something like this:
In the output, we can ignore the first two lines. This essentially means that the debugger was set up
correctly and it's able to listen to the app running in the background.
Next, we have our very first line break in playground debugging on line one, and right following to it you
can see line one with a little caret (>) next to it. When you first run your app in debug mode, it pauses
before it executes the first statement. When we're paused on a line like line one, that means the line has
not executed, so at this point in time we don't even have the person variable in place.
Now, as you can see in the preceding code, we haven't returned to the command line, Node is still waiting
for input, and there are a few different commands we can run. For example, we can run n, which is short
for next. You can type n, hit enter, and this moves on to the next statement.
The next statement we have, the statement on line one, was executed, so the person variable does exist.
Then I can use n again to go to the next statement where we declare the person.name property, updating
it from Andrew to Mike:
Notice, at this point, age does exist because that line has already been executed.
Now, the n command goes statement by statement through your entire program. If you realize that you
don't want to do that through the whole program, which could take a lot of time, you can use c. The c
command is short for Continue, and that continues to the very end of the program. In the following code,
you can see our console.log statement runs the name Mike and the age 25:
This is that's a quick example of how to use the debug keyword.
Now, we actually didn't do any debugging, we just ran through the program since it is a little foreign in
terms of writing these commands, such as next and continue, I decided to do a dry run once with no
debugging. You can use control
+ C to quit the debugger and get returned back to Terminal.
I'll use clear to clear all the output. Now that we have a basic idea about how we can execute the program
in debug mode, let's take a look at how we can actually do some debugging.
Working with debugging
I'll rerun the program using the up arrow key twice to return to the Node debug
command. Then, I'll run the program, and I'll hit next twice, n and n:
At this point in time, we are on line seven, that is where the line break currently is. From here we can do
some debugging using a command called repl, which stands for Read Evaluate Print Loop. The repl
command, in our case, brings you to an entirely separate area of the debugger. When you hit it you're
essentially in a Node console:
You can run any Node commands, for example, I can use console.log to print something like test, and test
prints up right there.
I can make a variable a that is equal to 1 plus 3, then I can reference a and I can see it's equal to 4 as
shown:
More importantly, we have access to the current program as it sits, meaning as it was before line seven
was executed. We can use this to print out person, and as shown in the following code, you can see the
person's name is Andrew because line seven hasn't executed and the age is 25, exactly as it appears in the
program:
This is where debugging gets really useful. Being able to look at the program paused at a certain point in
time is going to make it really easy to spot errors. I could do anything I want, I could print out the person
name property, and that prints Andrew to the screen, as shown here:
Now, once again, we still have this problem. I have to hit next through the program. When you have a
really long program, there could literally be hundreds or thousands of statements that need to run before
you get to the point you care about. Obviously that is not ideal, so we're going to look at a better way.
Let's quit repl using control + C; now we're back at the debugger.
From here we are going to make a quick change to our application in debugging.js.
Let's say we want to pause line seven between the person age property update and the person name
property update. In order to pause, what we're going to do is run the statement debugger:
var person = { name: 'Andrew'
};
person.age = 25;
debugger; person.name = 'Mike'; console.log(person);
When you have a debugger statement exactly like previous, it tells the Node debugger to stop here, which
means instead of using n (next) to go statement by statement, you can use c (continue), which is going to
continue until either the program exits or it sees one of the debugger keywords.
Now, over in Terminal, we're going to rerun the program exactly like we did before. This time around,
instead of hitting n twice, we're going to use c to continue:
Now, when we first used c, it went to the end of the program, printing out our object. This time around it's
going to continue until it finds that debugger keyword.
Now, we can use repl, access anything we like, for example, person.age, shown in this code:
Once we're done debugging, we can quit and continue through the program.
Again, we can use control + C to quit repl and the debugger.
All real debugging pretty much happens with the debugger keyword. You put it wherever you want on
your program, you run the program in debug mode, eventually it gets to the debugger keyword and you do
something. For example you explore some variable values, you run some functions, or you play around
with a code to find the error. No one really uses n to print through the program, finding the line that
causes the problem. That takes way too much time and it's just not realistic.
Using debugger inside the notes application
Now that you know a little bit about the debugger, I want you to use it inside our notes application. What
we will do inside notes.js is add the debugger statement in logNote function as the first line of the
function. Then I will run the program in debug mode, passing in some arguments that will cause logNote to
run; for example, reading a note, after the note gets fetched, it's going to call logNote.
Now, once we have the debugger keyword in the logNote function and run it in debug mode with those
arguments, the program should stop at this point. Once the program starts in debug mode, we'll use c to
continue, and it'll pause. Next, we'll print out the note object and make sure it looks okay. Then, we can
quit repl and quit the debugger.
Now, first we are adding the debugger statement right here:
var logNote = (note) => { debugger; console.log('--');
console.log(`Title: ${note.title}`); console.log(`Body: ${note.body}`);
};
We can save the file, and now we can move into Terminal; there's no need to do anything else inside our
app.
Inside Terminal we're going to run our app.js file, node debug app.js, because we want to run the program
in debug mode. Then we can pass in our arguments, let's say the read command, and I'll pass in a title, "to
buy" as shown here:
node debug app.js read --title="to buy"
In this case I have a note with the title "to buy", as shown here:
Now, when I run the preceding command, it's going to pause before that first statement runs, this is
expected:
I can now use c to continue through the program. It's going to run as many statements as it takes for either
the program to end or for the debugger keyword to be found, and as shown in the following code, you can
see the debugger was found and our program has stopped on line 49 of notes.js:
This is exactly what we wanted to do. Now, from here, I'll go into repl and print out note argument, and as
shown in the following code, you can see we have the note with the title of to buy and the body food:
Now, if there was an error in this statement, maybe the wrong thing was printing to the screen, this would
give us a pretty good idea as to why. Whatever gets passed into the note is clearly being used inside of the
console.log statements, so if there was an issue with what's printing, it's most likely an issue with what
gets passed into the logNote function.
Now that we've printed the note variable, we can shut down repl, and we can use
control + C or quit to quit the debugger.
Now we're back at the regular Terminal and we have successfully completed the debugging inside the
Node application. In the next section, we're going to look at a different way to do the same thing, a way
with a much nicer graphic user interface that I find a lot easier to navigate and use.
Listing notes
Now that we've made some awesome progress on debugging, let's go back to the commands for our app,
because there is only one more to fill out (we have covered the add, read, and remove commands in the
Chapter 3, Node Fundamentals - Part 2, and this chapter, respectively). It's the list command, and it's going
to be really easy, there is nothing complex going on in the case of the list command.
Using the getAll function
In order to get started, all we need to do is fill out the list notes function, which in this case we called
getAll. The getAll function is responsible for returning every single note. That means it's going to return an
array of objects, an array of all of our notes.
All we have to do that is to return fetchNotes, as shown here:
var getAll = () => { return fetchNotes();
}
There's no need to filter, there's no need to manipulate the data, we just need to pass the data from
fetchNotes back through getAll. Now that we have this in place, we can fill out the functionality over inside
of app.js.
We have to create a variable where we can store the notes, I was going to call it notes, but I probably
shouldn't because we already have a notes variable declared. I'll create another variable, called allNotes,
setting it equal to the return value from getAll, which we know because we just filled out returns all the
notes:
else if (command === 'list') { var allNotes = notes.getAll();
}
Now I can use console.log to print a little message and I'll use template strings so I can inject the actual
number of notes that are going to be printed.
Inside the template strings, I'll add Printing, then the number of notes using the $ (dollar) sign and the
curly braces, allNotes.length: that's the length of the array followed by notes with the s in parenthesis to
handle both singular and plural cases, as shown in the following code block:
else if (command === 'list') { var allNotes = notes.getAll();
console.log(`Printing ${allNotes.length} note(s).`);
}
So, if there were six notes, it would say printing six notes.
Now that we have this in place, we have to go about the process of actually printing each note, which
means we need to call logNote once for every item in the allNotes array. To do, this we'll use forEach,
which is an array method similar to filter.
Filter lets you manipulate the array by returning true or false to keep items or remove items; forEach
simply calls a callback function once for each item in the array. In this case we can use it using
allNotes.forEach, passing in a callback function. Now, that callback function will be an arrow function (=>)
in our case, and it will get called with the note variable just like filter would have. And all we'll call is
notes.logNote, passing in the note argument, which is right here:
else if (command === 'list') { var allNotes = notes.getAll();
console.log(`Printing ${allNotes.length} note(s).`); allNotes.forEach((note) => {
notes.logNote(note);
});
}
And now that we have this in place, we can actually simplify it by adding the
logNote call, as shown in here:
else if (command === 'list') { var allNotes = notes.getAll();
console.log(`Printing ${allNotes.length} note(s).`); allNotes.forEach((note) => notes.logNote(note));
}
This is the exact same functionality, only using the expression syntax. Now that we have our arrow
function (=>) in place, we are calling notes.logNote once for each item in the all notes array. Let's save the
app.js file and test this out over in Terminal.
In order to test out the list command, all I'll use is node app.js with the command
list. There is no need to pass in any arguments:
node app.js list
When I run this, I do get Printing 3 note(s) and then I get my 3 notes to buy, to buy from store, and things
to do, as shown in the following code output, which is fantastic:
With this in place, all of our commands are now working. We can add notes, remove notes, read an
individual note, and list all of the notes stored in our JSON file.
Moving on to the next section, I want to clean up some of the commands. Inside app.js and notes.js, we
have some console.log statements that are printing out a few things we no longer need.
At the very top of app.js, I am going to remove the console.log('Starting app.js')
statement, making the constant fs the first line.
I'll also remove the two statements: console.log('Command: ', command) and
console.log('Yargs', argv) that print the command and the yargs variable value.
Inside notes.js, I will also remove the console.log('Stating notes.js') statement at the very top of that file,
since it is no longer necessary, putting constant fs at the top.
It was definitely useful when we first started exploring different files, but now we have everything in place,
there's no need. If I rerun the list command, this time you can see it looks a lot cleaner:
Printing three notes is the very first line showing up. With this in place, we have done our commands.
In the next section, we're going to take a slightly more in-depth look at how we can configure yargs. This is
going to let us require certain arguments for our commands. So if someone tries to add a note without a
title, we can warn the user and prevent the program from executing.
Advanced yargs
Before we get into the advanced discussion of yargs, first, I want to pull up the yargs docs so that you at
least know where the information about yargs is coming from. You can get it by Googling npm yargs. We're
going to go to the yargs package page on npm. This has the documentation for yargs, as shown here:
Now there is no table of contents for the yargs docs, which makes it kind of difficult to navigate. It starts
off with some examples that don't go in any particular order, and then eventually it gets into a list of all the
methods you have available, and that's what we're looking for.
So I'll use command + F (Ctrl + F) to search the page for methods, and as shown
in the following screenshot, we get the methods header, which is the one we're looking for:
If you scroll down on the page, we start to see an alphabetical list of all the methods you have access to
inside of yargs. We're specifically looking for
.command; this is the method we can use to configure all four of our commands: the
add, read, remove and list notes:
We're going to specify which options they require, if any, and we can also set up things like descriptions
and help functionality.
Using chaining syntax on yargs
Now in order to get started, we need to make some changes inside of app.js. We're going to start with the
add command (for more information, please refer to the Adding and saving notes section in the previous
chapter).
We want to add a few helpful pieces of information in argv function inside app.js, that will:
Let yargs verify the add command is ran appropriately, and
Let the user know how the add command is meant to be executed
Now we are going to be chaining property calls, which means right before I access .argv I want to call
.command, and then I'll call .argv on the return value from command as shown here:
const argv = yargs
.command()
.argv;
Now this chaining syntax probably looks familiar if you've used jQuery, a lot of different libraries are
supported. Once we call .command on yargs, we're going to pass in three arguments.
The first one is the command name, exactly how the user is going to type it in Terminal, in our case it's
going to be add:
const argv = yargs
.command('add')
.argv;
Then we're going to pass another string in, and this is going to be a description of what the command does.
It is going to be some sort of English readable description that a user can read to figure out weather that's
the command that they want to run:
const argv = yargs
.command('add', 'Add a new note')
.argv;
The next one is going to be an object. This is going to be the options object that lets us specify what
arguments this command requires.
Calling the .help command
Now before we get into the options object, let's add one more call right after command. We're going to
call .help, which is a method, so we're going to call it as a function, and we don't need to pass in any
arguments:
const argv = yargs
.command('add', 'Add a new note', {
})
.help()
.argv;
When we add on this help call, it sets up yargs to return some really useful information when someone
runs the program. For example, I can run the node app.js command with the help flag. The help flag is
added because we called that help method, and when I run the program, you can see all of the options we
have available:
node app.js --help
As shown in the preceding output, we have one command, add Add a new note, and
a help option for the current command, help. And the same thing holds true if we run the node app.js add
command with help as shown here:
node app.js add --help
In this output, we can view all of the options and arguments for add command, which in this case happens
to be none because we haven't set those up:
Adding the options object
Let's add options and arguments back inside Atom. In order to add properties, we're going to update the
options object, where the key is the property name, whether it's title or body, and the value is another
object that lets us specify how that property should work, as shown here:
const argv = yargs
.command('add', 'Add a new note', { title: {
}
})
.help()
.argv;
Adding the title
In the case of title, we would add the title on the left-hand side, and we would put our options object on
the right-hand side. Inside the title, we're going to configure three properties describe, demand, and alias:
The describe property will be set equal to a string, and this is going to describe what is supposed to be
passed in for the title. In this case, we can just use Title of note:
const argv = yargs
.command('add', 'Add a new note', { title: {
describe: 'Title of note'
}
})
.help()
.argv;
Next we configure demand. It is going to tell yarg whether or not this argument is required. demand is
false by default, we'll set it to true:
const argv = yargs
.command('add', 'Add a new note', { title: {
describe: 'Title of note',
demand: true
}
})
.help()
.argv;
Now if someone tries to run the add command without the title, it's going to fail, and we can prove this. We
can save app.js, and in Terminal, we can rerun our previous command removing the help flag, and when I
do that, you see we get a warning, Missing required argument: title as shown here:
Notice that in the output the title argument, is Title of note, which is the describe string we used, and it's
required on the right side, letting you know that you have to provide a title when you're calling that add
command.
Along with describe and demand we are going to provide a third option, this is called alias. The alias lets
you provide a shortcut so you don't have to type -- title; you can set the alias equal to a single character
like t:
const argv = yargs
.command('add', 'Add a new note', { title: {
describe: 'Title of note', demand: true,
alias: 't'
}
})
.help()
.argv;
When you have done that, you can now run the command in Terminal using the new syntax.
Let's run our add command, node app.js add, instead of --title. We're going to use
-t, which is the flag version, and we can set that equal to whatever we like, for example, flag title will be
the title, and --body will get set equal to body , as shown in the following code. Note that we haven't set
up the body argument yet so there is no alias:
node app.js add -t="flag title" --body="body"
If I run this command, everything works as expected. The flag title shows up right where it should, even
though we used the alias version which is the letter t,
as shown here:
Adding the body
Now that we have our title configured, we can do the exact same thing for the body. We'll specify our
options object and provide those three arguments:
describe, demand, and alias for body:
const argv = yargs
.command('add', 'Add a new note', { title: {
describe: 'Title of note', demand: true,
alias: 't'
},
body: {
}
})
.help()
.argv;
The first one is describe and that one's pretty easy. describe is going to get set equal to a string, and in this
case Body of note will get the job done:
const argv = yargs
.command('add', 'Add a new note', { title: {
describe: 'Title of note', demand: true,
alias: 't'
},
body: {
describe: 'Body of note'
}
})
.help()
.argv;
The next one will be demand, and to add a note we are going to need a body. So we'll set demand equal to
true, just like we do up previous for title:
const argv = yargs
.command('add', 'Add a new note', { title: {
describe: 'Title of note', demand: true,
alias: 't'
},
body: {
describe: 'Body of note'
demand: true
}
})
.help()
.argv;
And last but not least is the alias. The alias is going to get set equal to a single letter, I'll use the letter b for
body:
const argv = yargs
.command('add', 'Add a new note', { title: {
describe: 'Title of note', demand: true,
alias: 't'
},
body: {
describe: 'Body of note' demand: true,
alias: 'b'
}
})
.help()
.argv;
With this in place, we can now save app.js and inside Terminal, we can take a moment to rerun node app.js
add with the help flag:
node app.js add --help
When we run this command, we should now see the body argument showing up, and you can even see it
shows the flag version, as shown in the following output, the alias -b (Body of note), and it is required:
Now I'll run node app.js add passing in two arguments t. I'll set that equal to t, and
b setting it equal to b.
When I run the command, everything works as expected:
node app.js add -t=t -b=b
As shown in the preceding output screenshot, a new note was created with a title of t and a body of b.
With this in place, we've now successfully completed the setup for the add command. We have our add
command title, a description, and the block that specifies the arguments for that command. Now we do
have three more commands to add support for, so let's get started doing that.
Adding support to the read and remove commands
On the next line, I'll call .command again, passing in the command name. Let's do the list command first
because this one is really easy, no arguments are required. Then we'll pass in the description for the list
command, List all notes, as shown here:
.command('list', 'List all notes')
.help()
.argv;
Next up, we'll call command again. This time we'll do the command for read. The
read command reads an individual note, so for the description for the read
command, we'll use something like Read a note:
.command('list', 'List all notes')
.command('read', 'Read a note')
.help()
.argv;
Now the read command does require the title argument. That means we are going to need to provide that
options object. I'll take title from add command, copy it, and paste it in the read command options object:
.command('list', 'List all notes')
.command('read', 'Read a note', { title: {
describe: 'Title of note', demand: true,
alias: 't'
}
})
.help()
.argv;
As you probably just noticed, we have repeated code. The title configuration just got copied and pasted
into multiple places. It would be pretty nice if this was DRY, if it was in one variable we could reference in
both locations, in add and read commands.
Will call command for remove, just following where we called the command for
read. Now, the remove command will have a description. We'll stick with something simple like Remove a
note, and we will be providing an options object:
.command('remove', 'Remove a note', {
})
Now I can add the options object identical to the read command. However, in that options object, I'll set
title equal to titleOptions, as shown here, to avoid the repetition of code:
.command('remove', 'Remove a note', { title: titleOptions
})
Adding the titleOption and bodyOption variables
Now I don't have the titleOptions object created yet so the code would currently fail, but this is the general
idea. We want to create the titleOptions object once and reference it in all the locations we use it, for add,
read and remove command. I can take titleOptions, and add it for read as well as for add command, as
shown here:
.command('add', 'Add a new note', { title: titleOptions,
body: {
describe: 'Body of note', demand: true,
alias: 'b'
}
})
.command('list', 'List all notes')
.command('read', 'Read a note', { title: titleOptions
})
.command('remove', 'Remove a note', { title: titleOptions
})
Now, just previous the constant argv, I can create a constant called titleOptions, and I can set it equal to
that object that we defined for add and read command earlier, which is describe, demand, and alias, as
shown here:
const titleOptions = { describe: 'Title of note', demand: true,
alias: 't'
};
We now have the titleOptions in place, and this will work as expected. We have the exact same
functionality we did before, but we now have the titleOptions in a separate object, which follows the DRY
principle we discussed in the Reading note section.
Now, we could also do the same thing for body. It might seem like overkill since we're only using it in only
one location, but if we're sticking to the pattern of breaking them out into variables, I'll do it in the case of
the body as well. Just
following the titleOptions constant, I can create the constant bodyOptions, setting it equal to the options
object we defined in the body, for add command in the previous subsection:
const bodyOptions = { describe: 'Body of note', demand: true,
alias: 'b'
};
With this in place, we are now done. We have add, read, and remove, all with their arguments set up
referencing the titleObject and bodyObject variables defined.
Testing the remove command
Let's test out the remove command in Terminal. I'll list out my notes using node app.js list, so I can see
which notes I have to remove:
node app.js list
I'll remove the note with the title t, using the node app.js remove command and our flag "t":
node app.js remove -t="t"
We'll remove the note with the title t, and as shown previous, Note was removed prints to the screen. And
if I use the up arrow key twice, I can list the notes out again, and you can see the note with the title t has
indeed gone:
Let's remove one more note using the node app.js remove command. This time we're going to use --title,
which is the argument name, and the note we're going to remove has the title flag title, as shown in this
code:
When I remove it, it says Note was removed, and if I rerun the list command, I can see that we have three
notes left, the note was indeed removed , as shown here:
And that is it for the notes application.
Arrow functions
In this section, you're going to learn the ins and outs of the arrow function. It's an ES6 feature, and we
have taken a little look at it. Inside notes.js we used it in a few basic examples to create methods such as
fetchNotes and saveNotes, and we also passed it into a few array methods like filter, and for each array,
we used it as the callback function that gets called once for every item in the array.
Now if you try to swap out all of the functions in a program with arrow functions, it's most likely not going
to work as expected because there are some differences between the two, and it's really important to
know what those differences are, so you can make the decision to use a regular ES5 function or an ES6
arrow function.
Using the arrow function
The goal in this section is to give you the knowledge to make that choice, and we'll kick things off by
creating a new file in the playground folder called arrow- function.js:
Inside this file, we're going to play around with a few examples, going over some of the subtleties to the
arrow function. Before we type anything inside of the file, I'll start up this file with nodemon, so every time
we make a change it automatically refreshes over in Terminal.
If you remember, nodemon is the utility we installed in Chapter 2, Node Fundamentals - Part 1. It was a
global npm module. The nodemon is the command to run, and then we just pass in the file path like we
would for any other Node command. As we're going into the playground folder, and the file itself is called
arrow-function.js, we'll run the following command:
nodemon playground/arrow-function.js
We'll run the file, and nothing prints to the screen, as shown in the following output, besides the nodemon
logs because we have nothing in the file:
To get started, in the arrowfunction.js file, we'll create a function called square, by making a variable called
square and setting it equal to an arrow function.
To make our arrow function (=>), we'll first provide the arguments inside parentheses. Since we'll be
squaring a number, we just need one number, and I'll refer to that number as x. If I pass in 3, I should
expect 9 back, and if I pass in 9, I would expect 81 back.
After the arguments list, we have to put the arrow in arrow function (=>) by putting the equal sign and the
greater than symbol together, creating our nice little arrow. From here we can provide, inside curly braces,
all the statements we want to execute:
var square = (x) => {
};
Next, we might create a variable called result, setting that equal to x times x, then we might return the
result variable using the return keyword, as shown here:
var square = (x) => { var result = x * x; return result;
};
Now, obviously this can be done on one line, but the goal here is to illustrate that when you use the
statement arrow function (=>), you can put as many lines as you want in between those curly braces. Let's
call a square, we'll do that using console.log so we can print the result to the screen. I'll call square; and
we'll call square with 9, the square of 9 would be 81, so we would expect 81 to print to the screen:
var square = (x) => { var result = x * x; return result;
};
console.log(square(9));
I'll save the arrow function (=>) file, and in Terminal, 81 shows up just as we expect:
Now the syntax we used in the previous example is the statement syntax for the arrow function (=>).
We've also explored the expression syntax earlier, which lets you simplify your arrow functions when you
return some expressions. In this case all we need to do is specify the expression we want to return. In our
case that's x times x:
var square = (x) => x * x; console.log(square(9));
You don't need to explicitly add the return keyword. When you use an arrow function (=>) without your
curly braces, it's implicitly provided for you. That means we can save the function as shown previous and
the exact same result is going to print to the screen, 81 shows up.
This is one of the great advantages of arrow functions when you use them in cases like filter or for those
which we did in the notes.js file. It lets you simplify your code keeping everything on one line and making
your code a lot easier to maintain and scan.
Now, there is one thing I want to note: when you have an arrow function (=>) that has just one argument,
you can actually leave off
the parentheses. If you have two or more arguments, or you have zero arguments, you are going to need to
provide the parentheses, but if you just have one argument, you can reference it with no parentheses.
If I save the file in this state, 81 still prints to the screen; and this is great we have an even simpler version
of our arrow function (=>):
Now that we have a basic example down, I want to move on to a more complex example that's going to
explore the nuances between regular functions and arrow functions.
Exploring the difference between regular and arrow functions
To illustrate the difference, I'll make a variable called user, which will be an object. On this object we'll
specify one property, name. Set name equal to the string, your name, in this case I'll set it equal to the
string Andrew:
var user = { name: 'Andrew'
};
Then we can define a method on the user object. Right after name, with my comma at the end of the line,
I'll provide the method sayHi, setting it equal to an arrow function (=>) that doesn't take any arguments.
For the moment, we'll keep the arrow function really simple:
var user = { name: 'Andrew', sayHi: () => {
}
};
All we'll do inside sayHi is use console.log to print to the screen, inside of template strings Hi:
var user = { name: 'Andrew', sayHi: () => {
console.log(`Hi`);
}
};
We're not using template strings yet, but we will later so I'll use them here. Down following the user
object, we can test out sayHi by calling it, user.sayHi:
var user = { name: 'Andrew', sayHi: () => {
console.log(`Hi`);
}
};
user.sayHi();
I'll call it then save the file, and we would expect that Hi prints to the screen because all our arrow function
(=>) does is use console.log to print a static string. Nothing in this case will cause any problems; you'd be
able to swap out a regular function for an arrow function (=>) without issue.
Now the first issue that will arise when you use arrow functions is the fact that arrow functions do not bind
a this keyword. So if you are using this inside your function, it's not going to work when you swap it out for
an arrow function (=>). Now, this binding; refers to the parent binding, in our case there is no parent,
function so this would refer to the global this keyword. Now we have our console.log that does not use
this, I'll swap it out for a case that does.
We'll put a period after Hi, and I'll say I'm, followed by the name, which we would usually be able to access
via this.name:
var user = { name: 'Andrew', sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHi();
If I try to run this code, it is not going to work as expected; we're going to get Hi
I'm undefined printing to the screen, as shown here:
In order to fix this, we'll look at an alternative syntax to arrow functions that's great when you're defining
object literals, as we are in this case.
After sayHi, I'll make a new method called sayHiAlt using a different ES6 feature. ES6 provides us a new
way to make methods on objects; you provide the method name, sayHiAlt, then you go right to the
parentheses skipping the colon. There's also no need for the function keyword, even though it is a regular
function it's not an arrow function (=>). Then we move on to our curly braces as shown here:
var user = { name: 'Andrew', sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() {
}
};
user.sayHi();
Inside here I can have the exact same code we have in the sayHi function, but it is going to work as
expected. It's going to print Hi. I'm Andrew. I'll call sayHiAlt down following instead of the regular sayHi
method:
var user = { name: 'Andrew', sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() {
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHiAlt();
And in Terminal, you can see Hi. I'm Andrew, prints to the screen:
The sayHiAlt syntax is a syntax that you can use to solve this problem when you create functions on object
literals. Now that we know that the this keyword does not get bound, let's explore one other quirk that
arrow functions have, it also does not bind the arguments array.
Exploring the arguments array
Regular functions, like sayHiAlt, are going to have an arguments array that's accessible inside of the
function:
var user = { name: 'Andrew', sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() {
console.log(arguments);
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHiAlt();
Now, it's not an actual array, it's more like an object with array; like properties, but the arguments object is
indeed specified in a regular function. If I pass in one, two, and three and save the file, we'll get that back
when we log out arguments:
var user = { name: 'Andrew', sayHi: () => {
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() { console.log(arguments);
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHiAlt(1, 2, 3);
Inside nodemon, it's taking a quick second to restart, and right here we have our object:
We have one, two, and three, we have the index for each as the property name, and this works because
we're using a regular function. If we were to switch to the arrow function (=>) though, it is not going to
work as expected.
I'll add console.log(arguments) inside of my arrow function (=>), and I'll switch from calling sayHiAlt back to
the original method sayHi, as shown here:
var user = { name: 'Andrew', sayHi: () => {
console.log(arguments);
console.log(`Hi. I'm ${this.name}`);
},
sayHiAlt() { console.log(arguments);
console.log(`Hi. I'm ${this.name}`);
}
};
user.sayHi(1, 2, 3);
When I save the file in arrow-function.js, we'll get something a lot different from what we had before.
What we'll actually get is the global arguments variable, which is the arguments variable for that wrapper
function we explored:
In the previous screenshot, we have things like the require function, definition, our modules object, and a
couple of string paths to the file and to the current directory. These are obviously not what we're
expecting, and that is another thing that you have to be aware of when you're using arrow functions;
you're not going to get the arguments keyword, you're not going to get the this binding (defined in sayHi
syntax) that you'd expect.
These problems mostly arise when you try to create methods on an object and use arrow functions. I
would highly recommend that you switch to sayHiAlt syntax which we discussed, in those cases. You get a
simplified syntax, but you also get the disk binding and you get your arguments variable as you'd expect.
Summary
In this chapter, we were able to reuse the utility functions that we already made in previous chapters,
making the process of filling out a remove note that much easier. Inside app.js, we worked on how the
removeNote function is executed, if it was executed successfully, we print a message; if it didn't, we print a
different message.
Next, we were able to successfully fill out the read command and we also created a really cool utility
function that we can take advantage of in multiple places. This keeps our code DRY and prevents us from
having the same code in multiple places inside of our application.
Then we discussed a quick introduction to debugging. Essentially, debugging is a process that lets you stop
the program at any point in time and play around with the program as it exists at that moment. That
means you can play around with variables that exist, or functions, or anything inside of Node. We learned
more about yargs, its configuration, setting up commands, their description, and arguments.
Last, you explored a little bit more about arrow functions, how they work, when to use them, and when
not to use them. In general, if you don't need this keyword, or the arguments keyword you can use an
arrow function without a problem, and I always prefer using arrow functions over regular functions when I
can.
In the next chapter, we will explore asynchronous programming and how we can fetch data from third-
party APIs. We'll use both regular functions and arrow functions a lot more, and you'll be able to see
firsthand how to choose between one over the other.
Basics of Asynchronous Programming in Node.js
If you've read any article about Node, you'd have probably come across four terms: asynchronous, non-
blocking, event-based, and single-threaded. All of those are accurate terms to describe Node; the problem
is it usually stops there, and it's really abstract. The topic of asynchronous programming in Node.js has
been divided into three chapters. The goal in these upcoming three chapters is to make asynchronous
programming super practical by putting all these terms to use in our weather application. That's the
project we're going to be building in these chapters.
This chapter is all about the basics of asynchronous programming. We'll look into the basic concepts,
terms, and technology related to async programming. We'll look into making requests to Geolocation APIs.
We'll need to make asynchronous HTTP requests. Let's dive in, looking at the very basics of async
programming in Node.
Specifically, we'll look into the following topics:
The basic concept of asynchronous program Call stack and event loop
Callback functions and APIs HTTPS requests
The basic concept of asynchronous program
In this section, we're going to create our first asynchronous non-blocking program. This means our app will
continue to run while it waits for something else to happen. In this section, we'll look at a basic example;
however, in the chapter, we'll be building out a weather app that communicates with third-party APIs,
such as the Google API and a weather API. We'll need to use asynchronous code to fetch data from these
sources.
For this, all we need to do is make a new folder on the desktop for this chapter. I'll navigate onto my
desktop and use mkdir to make a new directory, and I'll call this one weather-app. All I need to do is
navigate into the weather app:
Now, I'll use the clear command to clear the Terminal output.
Now, we can open up that new weather app directory inside of Atom:
This is the directory we'll use throughout this entire chapter. In this section, we'll not be building out the
weather app just yet, we'll just play around with the async features. So inside weather-app we'll make the
playground folder.
This code is not going to be a part of the weather app, but it will be really useful when it comes to creating
the weather app in the later sections. Now inside playground, we can make the file for this section. We'll
name it async-basics.js as shown here:
Illustrating the async programming model
To illustrate how the asynchronous programming model works, we'll get started with a simple example
using console.log. Let's get started by adding a couple of console.log statements in a synchronous way.
We'll create one console.log statement at the beginning of the app that will say Starting app, and we will
add a second one to the end, and the second one will print Finishing up, as shown here:
console.log('Starting app'); console.log('Finishing up');
Now these are always going to run synchronously. No matter how many times you run the program,
Starting app is always going to show up before Finishing up.
In order to add some asynchronous code, we'll take a look at a function that Node provides called
setTimeout. The setTimeout function is a great method for illustrating the basics of non-blocking
programming. It takes two arguments:
The first one is a function. This will be referred to as callback function, and it will get fired after a certain
amount of time.
The second argument is a number, which tells the number of milliseconds you want to wait. So if you want
to wait for one second, you would pass in a thousand milliseconds.
Let's call setTimeout, passing in an arrow function (=>) as our first argument. This will be callback function.
It will get fired right away; that is, it will get fired after the timeout is up, after our two seconds. And then
we can set up our second argument which is the delay, 2000 milliseconds, which equals those two
seconds:
console.log('Starting app'); setTimeout(() => {
}, 2000);
Inside the arrow function (=>), all we'll do is use a console.log statement so that we
can figure out exactly when our function fires, because the statement will print to the screen. We'll add
console.log and then inside callback to get the job done, as shown here:
setTimeout(() => { console.log('Inside of callback');
}, 2000);
With this in place, we're actually ready to run our very first async program, and I'll not use nodemon to
execute it. I'll run this file from the Terminal using the basic Node command; node playground and the file
inside the playground folder which is
async-basic.js:
node playground/async-basics.js
Now pay close attention to exactly what happens when we hit enter. We'll see two messages show up
right away, then two seconds later our final message, Inside of callback, prints to the screen:
The sequence in which these messages are shown is: first we got Starting app; almost immediately after
this, Finishing up prints to the screen and finally (two seconds later), Inside of callback was printed as
shown in the previous code. Inside the file, this is not the order in which we wrote the code, but it is the
order the code executes in.
The Starting app statement prints to the screen as we expect. Next, we call setTimeout, but we're not
actually telling it to wait two seconds. We're registering a callback that will get fired in two seconds. This
will be an asynchronous callback, which means that Node can do other things while these two seconds are
happening. In this case, the other thing it moves down to the Finishing up message. Now since we did
register this callback by using setTimeout, it will fire at some point in time, and two seconds later we do
see Inside of callback printing
to the screen.
By using non-blocking I/O, we're able to wait, in this case two seconds, without preventing the rest of the
program from executing. If this was blocking I/O, we would have to wait two seconds for this code to fire,
then the Finishing up message would print to the screen, and obviously that would not be ideal.
Now this is a pretty contrived example, we will not exactly use setTimeout in our real-world apps to create
unnecessary arbitrary delays, but the principles are the same. For example, when we fetch data from the
Google API we'll need to wait about 100 to 200 milliseconds for that data to come back, and we don't want
the rest of the program to just be idle, it will continue. We'll register a callback, and that callback will get
fired once the data comes back from the Google servers. The same principles applies even though what's
actually happening is quite different.
Now, we want to write another setTimeout right here. We want to register a
setTimeout function that prints a message; something like Second setTimeout works. This will be inside the
callback, and we want to register a delay of 0 milliseconds, no delay at all. Let's fill out the async basics
setTimeout. I'll call setTimeout with my arrow function (=>), passing in a delay of 0 milliseconds, as shown
in the following code. Inside the arrow function (=>), I'll use console.log so I can see exactly when this
function executes, and I'll use Second setTimeout as the text:
setTimeout(() => { console.log('Second setTimeout');
}, 0);
Now that we have this in place, we can run the program from the Terminal, and it's really important to pay
attention to the order in which the statements print. Let's run the program:
node playground/async-basics.js
Right away we get three statements and then at the very end, two seconds later, we get our final
statement:
We start with Starting app, which makes sense, it's at the top. Then we get Finishing up. After Finishing up
we get Second setTimeout, which seems weird, because we clearly told Node we want to run this function
after 0 milliseconds, which should run it right away. But in our example, Second setTimeout printed after
Finishing up.
Finally, Inside of callback printed to the screen. This behavior is completely expected. This is exactly how
Node.js is supposed to operate, and it will become a lot clearer after the next section, where we'll go
through this example exactly, showing you what happens behind the scenes. We'll get started with a more
basic example showing you how the call stack works, we'll talk all about that in the next section, and then
we'll go on to a more complex example that has some asynchronous events attached to it. We'll discuss
the reason why Second setTimeout comes up after the Finishing up message after the next section.
Call stack and event loop
In the last section, we ended up creating our very first asynchronous application, but unfortunately we
ended up asking more questions than we got answers. We don't exactly know how async programming
works even though we've used it. Our goal for this section is to understand why the program runs the way
it does.
For example, why does the two-second delay in the following code not prevent the rest of the app from
running, and why does a 0 second delay cause the function to be executed after Finishing up prints to the
screen?
console.log('Starting app'); setTimeout(() => {
console.log('Inside of callback');
}, 2000);
setTimeout(() => { console.log('Second setTimeout');
}, 0);
console.log('Finishing up');
These are all questions we'll answer in this section. This section will take you behind the scenes into what
happens in V8 and Node when an async program runs. Now let's dive right into how the async program
runs. We'll start with some basic synchronous examples and then move on to figuring out exactly what
happens in the async program.
A synchronous program example
The following is example number one. On the left-hand side we have the code, a basic synchronous
example, and on the right-hand side we have everything that happens behind the scenes, the Call Stack,
our Node APIs, the Callback Queue, and the Event Loop:
Now if you've ever read an article or watched any video lesson on how Node works, you've most likely
heard about one or more of these terms. In this section, we'll be exploring how they all fit together to
create a real-world, working Node application. Now for our first synchronous example, all we need to
worry about is the Call Stack. The Call Stack is part of a V8, and for our synchronous example it's the only
thing that's going to run. We're not using any Node APIs and we're not doing any asynchronous
programming.
The call stack
The Call Stack is a really simple data structure that keeps track of program execution inside of a V8. It
keeps track of the functions currently executing and the statements that are fired. The Call Stack is a really
simple data structure that can do two things:
You can add something on top of it You can remove the top item
This means if there's an item at the bottom of the data structure and there's an item above it, you can't
remove the bottom item, you have to remove the top item. If there's already two items and you want to
add something on to it, it has to go on because that's how the Call Stack works.
Think about it like a can of Pringles or a thing of tennis balls: if there's already an item in there and you
drop one in, the item you just dropped will not be the bottom item, it's going to be the top item. Also, you
can't remove the bottom tennis ball from a can of tennis balls, you have to remove the one on top first.
That's exactly how the Call Stack works.
Running the synchronous program
Now when we start executing the program shown in the following screenshot, the first thing that will
happen is Node will run the main function. The main function is the wrapper function we saw over in
nodemon (refer to, Installing the nodemon module section in Chapter 2, Node Fundamentals Part-1) that
gets wrapped around all of our files when we run them through Node. In this case, by telling V8 to run the
main function we are starting the program.
As shown in the following screenshot, the first thing we do in the program is create a variable x, setting it
equal to 1, and that's the first statement that's going to run:
Notice it comes in on top of main. Now this statement is going to run, creating the variable. Once it's done,
we can remove it from the Call Stack and move on to the next statement, where we make the variable y,
which gets set equal to x, which is 1 plus 9. That means y is going to be equal to 10:
As shown in the previous screenshot, we do that and move on to the next line. The next line is our
console.log statement. The console.log statement will print y is 10 to the screen. We use template strings
to inject the y variable:
console.log(`y is ${y}`);
When we run this line it gets popped on to the Call Stack, as shown here:
Once the statement is done, it gets removed. At this point, we've executed all the statements inside our
program and the program is almost ready to be complete. The main function is still running but since the
function ends, it implicitly returns, and when it returns, we remove main from the Call Stack and the
program is finished. At this point, our Node process is closed. Now this is a really basic example of using
the Call Stack. We went into the main function, and we moved line by line through the program.
A complex synchronous program example
Let's go over a slightly more complex example, our second example. As shown in the following code, we
start off by defining an add function. The add function takes arguments a and b, adds them together
storing that in a variable called total, and returns total. Next, we add up 3 and 8, which is 11, storing it in
the res variable. Then, we print out the response using the console.log statement, as shown here:
var add = (a, b) => { var total = a + b;
return total;
};
var res = add(3, 8); console.log(res);
That's it, nothing synchronous is happening. Once again we just need the Call Stack. The first thing that
happens is we execute the main function; this starts the program we have here:
Then we run the first statement where we define the add variable. We're not actually executing the
function, we're simply defining it here:
In the preceding image, the add() variable gets added on to the Call Stack, and we define add. The next
line, line 7, is where we call the add variable storing the return value on the response variable:
When you call a function, it gets added on top of the Call Stack. When you return from a function, it gets
removed from the Call Stack.
In this example, we'll call a function. So we're going to add add() on to the Call Stack, and we'll start
executing that function:
As we know, when we add main we start executing main and, when we add add() we start executing add.
The first line inside add sets the total variable equal to a + b, which would be 11. We then return from the
function using the return total
statement. That's the next statement, and when this runs, add gets removed:
So when return total finishes, add() gets removed, then we move on to the final line in the program, our
console.log statement, where we print 11 to the screen:
The console.log statement will run, print 11 to the screen and finish the execution, and now we're at the
end of the main function, which gets removed from the stack when we implicitly return. This is the second
example of a program running through the V8 Call Stack.
An async program example
So far we haven't used Node APIs, the Callback Queue, or the Event Loop. The next example will use all
four (Call Stack, the Node APIs, the Callback Queue, and the Event Loop). As shown on the left-hand side of
the following screenshot, we have our async example, exactly the same as we wrote it in the last section:
In this example, we will be using the Call Stack, the Node APIs, the Callback Queue, and the Event Loop. All
four of these are going to come into play for our asynchronous program. Now things are going to start off
as you might expect. The first thing that happens is we run the main function by adding it on to the Call
Stack. This tells a V8 to kick off the code we have on the left side in the previous screenshot, shown here
again:
console.log('Starting app'); setTimeout(() => {
console.log('Inside of callback');
}, 2000);
setTimeout(() => { console.log('Second setTimeout');
}, 0);
console.log('Finishing up');
The first statement in this code is really simple, a console.log statement that prints
Starting app to the screen:
This statement runs right away and we move on to the second statement. The second statement is where
things start to get interesting, this is a call to setTimeout, which is indeed a Node API. It's not available
inside a V8, it's something that Node gives us access to:
The Node API in async programming
When we call the setTimeout (2 sec) function, we're actually registering the event callback pair in the Node
APIs. The event is simply to wait two seconds, and the callback is the function we provided, the first
argument. When we call setTimeout, it gets registered right in the Node APIs as shown here:
Now this statement will finish up, the Call Stack will move on, and the setTimeout will start counting down.
Just because the setTimeout is counting down, it doesn't mean the Call Stack can't continue to do its job.
The Call Stack can only run one thing at a time, but we can have events waiting to get processed even
when the Call Stack is executing. Now the next statement that runs is the other call to
setTimeout:
In this, we register a setTimeout callback function with a delay of 0 milliseconds, and the exact same thing
happens. It's a Node API and it's going to get registered as shown in the following screenshot. This
essentially says that after zero seconds, you can execute this callback:
The setTimeout (0 sec) statement gets registered and the Call Stack removes that statement.
The callback queue in async programming
At this point let's assume that setTimeout, the one that has a zero second delay, finishes. When it finishes,
it's not going to get executed right away; it's going to take that callback and move it down into the Callback
Queue, as shown here:
The Callback Queue is all the callback functions that are ready to get fired. In the previous screenshot, we
move the function from Node API into the Callback Queue. Now the Callback Queue is where our callback
functions will wait; they need to wait for the Call Stack to be empty.
When the Call Stack is empty we can run the first function. There's another function after it. We'll have to
wait for that first function to run before the second one does, and this is where the Event Loop comes into
play.
The event loop
The Event Loop takes a look at the Call Stack. If the Call Stack is not empty, it doesn't do anything because
it can't, there is nothing it can do you can only run one thing at a time. If the Call Stack is empty, the Event
Loop says great let's see if there's anything to run. In our case, there is a callback function, but because we
don't have an empty Call Stack, the Event Loop can't run it. So let's move on with the example.
Running the async code
The next thing that happens in our program is we run our console.log statement, which prints Finishing up
to the screen. This is the second message that shows up in the Terminal:
This statement runs, our main function is complete, and it gets removed from the Call Stack.
At this point, the Event Loop says hey I see that we have nothing in the call stack and we do have
something in the Callback Queue, so let's run that callback function. It will take the callback and move it
into the Call Stack; this means the function is executing:
It will run the first line which is sitting on line 8, console.log, printing Second setTimeout to the screen. This
is why Second setTimeout shows up after Finishing up in our previous section examples, because we can't
run our callback until the Call Stack is complete. Since Finishing up is part of the main function, it will
always run before Second setTimeout.
After our Second setTimeout statement finishes, the function is going to implicitly return and callback will
get removed from the Call Stack:
At this point, there's nothing in the Call Stack and nothing in the Callback Queue, but there is still
something in our Node APIs, we still have an event listener registered. So the Node process is not yet
completed. Two seconds later, the setTimeout(2 sec) event is going to fire, and it's going to take that
callback function and move it into the Callback Queue. It gets removed from the Node APIs and it gets
added to the Callback Queue:
At this point, the Event Loop will take a look at the Call Stack and see it's empty. Then it will take a quick
look at the Callback Queue and see there is indeed something to run. What will it do? It will take that
callback, add it on to the Call Stack, and start the process of executing it. This means that we'll run our one
statement inside callback. After that's finished, the callback function implicitly returns and our program is
complete:
This is exactly how our program ran. This illustrates how we're able to register our events using Node APIs,
and why when we use a setTimeout of zero the code doesn't run right away. It needs to go through the
Node APIs and through the Callback Queue before it can ever execute on the Call Stack.
Now as I mentioned in the beginning of this section, the Call Stack, the Node APIs, the Callback Queue, and
the Event Loop are pretty confusing topics. A big reason why they're confusing is because we never
actually directly interact with them; they're happening behind the scenes. We're not calling the Callback
Queue, we're not firing an Event Loop method to make these things work. This means we're not aware
they exist until someone explains them. These are topics that are really hard to grasp the first time around.
By writing real asynchronous code it's going to become a lot clearer how it works.
Now that we got a little bit of an idea about how our code executes behind the
scenes, we'll move on with the rest of the chapter and start creating a weather app that interacts with
third-party APIs.
Callback functions and APIs
In this section, we'll take an in-depth look at callback functions, and use them to fetch some data from a
Google Geolocation API. That's going to be the API that takes an address and returns the latitude and
longitude coordinates, and this is going to be great for the weather app. This is because the weather API
we use requires those coordinates and it returns the real-time weather data, such as the temperature,
five-day forecast, wind speed, humidity, and other pieces of weather information.
The callback function
Before we get started making the HTTPS request, let's talk about callback functions, and we have already
used them. Refer to the following code (we used it in the previous section):
console.log('Starting app'); setTimeout(() => {
console.log('Inside of callback');
}, 2000);
setTimeout(() => { console.log('Second setTimeout');
}, 0);
console.log('Finishing up');
Inside the setTimeout function we used a callback function. In general, a callback function is defined as a
function that gets passed as an argument to another function and is executed after some event happens.
Now this is a general definition, there is no strict definition in JavaScript, but it does satisfy the function in
this case:
setTimeout(() => { console.log('Inside of callback');
}, 2000);
Here we have a function and we pass it as an argument to another function, setTimeout, and it does get
executed after some event—two-second pass. Now the event could be other things, it could be a database
query finishes, it could be an HTTP request comes back. In those cases, you will want a callback function,
like the one in our case, to do something with that data. In the case of setTimeout, we don't get any data
back because we're not requesting any; we're just creating an arbitrary delay.
Creating the callback function
Now before we actually make an HTTP request to Google, let's create a callback function example inside
our playground folder. Let's make a new file called
callbacks.js:
Inside the file, we'll create a contrived example of what a callback function would look like behind the
scenes. We'll be making real examples throughout the book and use many functions that require callbacks.
But for this chapter, we'll start with a simple example.
To get started, let's make a variable called getUser. This will be the function we'll define that will show us
exactly what happens behind the scenes when we pass a callback to another function. The getUser
callback will be something that simulates what it would look like to fetch a user from a database or some
sort of web API. It will be a function, so we'll set it as such using arrow function (=>):
var getUser = () => {
};
The arrow function (=>) is going to take some arguments. The first argument it will take is the id, which will
be some sort of a unique number that represents each user. I might have an id of 54, you might have an id
of 2000; either way we're going to need the id to find a user. Next up we'll get a callback function, which is
what we will call later with the data, with that user object:
var getUser = (id, callback) => {
};
This is exactly what happens when you pass a function to setTimeout.
The setTimeout function definition looks like this:
var getUser = (callback, delay) => {
};
It has a callback and a delay. You take the callback, and after a certain amount of time passes, you call it. In
our case, though, we'll switch the order with an id first and the callback second.
Now we can call this function before actually filling it out. We'll call getUser, just like we did with
setTimeout in the previous code example. I'll call getUser, passing in those two arguments. The first one
will be some id; since we're faking it for now it doesn't really matter, and I'll go with 31. The second
argument will be the function that we want to run when the user data comes back, and this is really
important. As shown, we'll define that function:
getUser(31, () => {
});
Now the callback alone isn't really useful; being able to run this function after the user data comes back
only works if we actually get the user data, and that's what we'll expect here:
getUser(31, (user) => {
});
We'll expect that the user objects, things like id, name, email, password, or whatever, comes back as an
argument to the callback function. Then inside the arrow function (=>), we can actually do something with
that data, for example, we could show it on a web app, respond to an API request, or in our case we can
simply
print it to the console, console.log(user):
getUser(31, (user) => { console.log(user);
});
Now that we have the call in place, let's fill out the getUser function to work like we have it defined.
The first thing I'll do is create a dummy object that's going to be the user object. In the future, this is going
to come from database queries, but for now we'll just create a variable user setting it equal to some
object:
var getUser = (id, callback) => { var user = {
}
};
Let's set an id property equal to whatever id the user passes in, and we'll set a name
property equal to some name. I'll use Vikram:
var getUser = (id, callback) => { var user = {
id: id,
name: 'Vikram'
};
};
Now that we have our user object, what we want to do is call the callback, passing it as an argument. We'll
then be able to actually run, getUser(31, (user) function, printing the user to the screen. In order to do this,
we would call the callback function like any other function, simply referencing it by name and adding our
parentheses like this:
var getUser = (id, callback) => { var user = {
id: id,
name: 'Vikram'
};
callback();
};
Now if we call the function like this, we're not passing any data from getUser back to the callback. In this
case, we're expecting a user to get passed back, which is why we are going to specify user as shown here:
callback(user);
Now the naming isn't important, I happen to call it user, but I could easily call this userObject and
userObject as shown here:
callback(user);
};
getUser(31, (userObject) => { console.log(userObject);
});
All that matters is the arguments, position. In this case, we call the first argument userObject and the first
argument pass back is indeed that userObject. With this in place we can now run our example.
Running the callback function
In the Terminal, we'll run the callback function using node, which is in the
playground folder, and we call the file callbacks.js:
node playground/callback.js
When we run the file, right away our data prints to the screen:
We've created a callback function using synchronous programming. Now as I mentioned, this is still a
contrived example because there is no need for a callback in this case. We could simply return the user
object, but in that case, we wouldn't be using a callback, and the whole point here is to explore what
happens behind the scenes and how we actually call the function that gets passed in as an argument.
Simulating delay using setTimeout
Now, we can also simulate a delay using setTimeout, so let's do that. In our code, just before the callback
(user) statement, we'll use setTimeout just like we did before in the previous section. We'll pass an arrow
function (=>) in as the first argument, and set a delay of 3 seconds using 3000 milliseconds:
setTimeout(() => {
}, 3000);
callback(user);
};
Now I can take my callback call, delete it from line 10, and add it inside of the callback function, as shown
here:
setTimeout(() => { callback(user);
}, 3000);
};
Now we'll not be responding to the getUser request until three seconds have passed. Now this will be
more or less similar to what happens when we create real-world examples of callbacks, we pass in a
callback, some sort of delay happens whether we're requesting from a database or from an HTTP
endpoint, and then the callback gets fired.
If I save callbacks.js and rerun the code from the Terminal, you'll see we wait those three seconds, which is
the simulated delay, and then the user object prints to the screen:
This is exactly the principle that we need to understand in order to start working with callbacks, and that is
exactly what we'll start doing in this section.
Making request to Geolocation API
The requests that we'll be making to that Geolocation API can actually be simulated over in the browser
before we ever make the request in Node, and that's exactly what we want to do to get started. So follow
along for the URL, htt ps://maps.googleapis.com/maps/api/geocode/json.
Now this is the actual endpoint URL, but we do have to specify the address for which we want the
geocode. We'll do that using query strings, which will be provided right after the question mark. Then, we
can set up a set of key value pairs and we can add multiples using the ampersand in the URL, for example:
htt ps://maps.googleapis.com/maps/api/geocode/json?key=value&keytwo=valuetwo.
In our case, all we need is one query string address, https://maps.googleapis.com/maps
/api/geocode/json?address, and for the address query string we'll set it equal to an address. In order to fill
out that query address, I'll start typing 1301 lombard street philadelphia.
Notice that we are using spaces in the URL. This is just to illustrate a point: we can use spaces in the
browser because it's going to automatically convert those spaces to something else. However, inside Node
we'll have to take care of that ourselves, and we'll talk about that a little later in the section. For now if we
leave the spaces in, hit enter, and we can see they automatically get converted for us:
Space characters get converted to %20, which is the encoded version of a space. In this page, we have all
of the data that comes back:
Now we'll use an extension called JSONView, which is available for Chrome and Firefox.
I highly recommend installing JSONView, as we should see a much nicer version of our JSON data. It lets us
minimize and expand various properties, and it makes it super easy to navigate.
Now as shown in the preceding screenshot, the data on this page has exactly what we need. We have an
address_components property, we don't need that. Next, we have a formatted address which is really
nice, it includes the state, the zip code, and the country, which we didn't even provide in the address
query.
Then, we have what we really came for: in geometry, we have location, and this includes the latitude
and longitude data.